From 66e9f9bc1e13c9a688a8a7ef95ec5913fdb967b5 Mon Sep 17 00:00:00 2001 From: kshku <1211shree@gmail.com> Date: Mon, 8 Jun 2026 12:37:44 +0530 Subject: [PATCH 1/8] feat: add SnukError types for structured error reporting --- include/snuk/snuk_error.h | 96 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 include/snuk/snuk_error.h diff --git a/include/snuk/snuk_error.h b/include/snuk/snuk_error.h new file mode 100644 index 0000000..c2be95e --- /dev/null +++ b/include/snuk/snuk_error.h @@ -0,0 +1,96 @@ +#pragma once + +#include + +typedef struct { + const char *file; // filename or NULL + uint32_t line; // 1-indexed + uint32_t col; // 1-indexed +} SnukSrcLoc; + +#define SNUK_SRC_LOC_NULL ((SnukSrcLoc){NULL, 0, 0}) + +typedef enum { + SNUK_ERROR_KIND_NONE = 0, + SNUK_ERROR_KIND_PARSE, + SNUK_ERROR_KIND_INTERP, +} SnukErrorKind; + +typedef enum { + SNUK_INTERP_ERR_NONE = 0, + SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, + SNUK_INTERP_ERR_SOMETHING_WENT_WRONG, + SNUK_INTERP_ERR_CONTROL_FLOW, + SNUK_INTERP_ERR_EXISTS, + SNUK_INTERP_ERR_NON_TYPE, + SNUK_INTERP_ERR_EXPECT_ASSIGN, + SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE, + SNUK_INTERP_ERR_MEMBER_INITIALIZE, + SNUK_INTERP_ERR_SELF_CREATION, + SNUK_INTERP_ERR_PARAM_CREATION, + SNUK_INTERP_ERR_NON_FN, + SNUK_INTERP_ERR_PARAM_COUNT, + SNUK_INTERP_ERR_NO_PARAM, + SNUK_INTERP_ERR_PARAM_MIXED, + SNUK_INTERP_ERR_PARAM_REQUIRED, + SNUK_INTERP_ERR_SELF, + SNUK_INTERP_ERR_SET_ENV_FAIL, + SNUK_INTERP_ERR_MEMBER, + SNUK_INTERP_ERR_INTERFACE, + SNUK_INTERP_ERR_TYPE_MISMATCH, +} SnukInterpError; + +typedef enum { + SNUK_PARSE_ERR_NONE = 0, + SNUK_PARSE_ERR_UNEXPECTED_TOKEN, + SNUK_PARSE_ERR_EXPECTED_IDENTIFIER, + SNUK_PARSE_ERR_EXPECTED_TYPE_ANNOTATION, + SNUK_PARSE_ERR_EXPECTED_SEMICOLON_OR_NEWLINE, + SNUK_PARSE_ERR_EXPECTED_CLOSE_BRACE, + SNUK_PARSE_ERR_EXPECTED_CLOSE_PAREN, + SNUK_PARSE_ERR_EXPECTED_CLOSE_BRACKET, + SNUK_PARSE_ERR_EXPECTED_KEYWORD, + SNUK_PARSE_ERR_EXPECTED_EXPRESSION, + SNUK_PARSE_ERR_EXPECTED_TYPE, + SNUK_PARSE_ERR_EXPECTED_MEMBER, + SNUK_PARSE_ERR_INVALID_LITERAL, + SNUK_PARSE_ERR_UNTERMINATED_STRING, + SNUK_PARSE_ERR_LEXER_ERROR, + SNUK_PARSE_ERR_UNKNOWN, +} SnukParseError; + +typedef struct { + SnukErrorKind kind; + uint32_t code; // cast from SnukInterpError or SnukParseError + const char *msg; // human-readable description + SnukSrcLoc loc; +} SnukError; + +#define SNUK_ERROR_NONE ((SnukError){SNUK_ERROR_KIND_NONE, 0, NULL, SNUK_SRC_LOC_NULL}) + +static inline const char *snuk_interp_error_msg(SnukInterpError code) { + switch (code) { + case SNUK_INTERP_ERR_NONE: return "no error"; + case SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE: return "shouldn't reach here"; + case SNUK_INTERP_ERR_SOMETHING_WENT_WRONG: return "something went wrong"; + case SNUK_INTERP_ERR_CONTROL_FLOW: return "control flow item outside scope"; + case SNUK_INTERP_ERR_EXISTS: return "variable already exists"; + case SNUK_INTERP_ERR_NON_TYPE: return "expected a type"; + case SNUK_INTERP_ERR_EXPECT_ASSIGN: return "expected assignment expression"; + case SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE: return "invalid value for builtin type member"; + case SNUK_INTERP_ERR_MEMBER_INITIALIZE: return "failed to initialize member"; + case SNUK_INTERP_ERR_SELF_CREATION: return "failed to create self"; + case SNUK_INTERP_ERR_PARAM_CREATION: return "failed to create parameter"; + case SNUK_INTERP_ERR_NON_FN: return "call expression on non-function"; + case SNUK_INTERP_ERR_PARAM_COUNT: return "parameter count mismatch"; + case SNUK_INTERP_ERR_NO_PARAM: return "parameter doesn't exist"; + case SNUK_INTERP_ERR_PARAM_MIXED: return "mixed positional and named parameters"; + case SNUK_INTERP_ERR_PARAM_REQUIRED: return "required parameter missing"; + case SNUK_INTERP_ERR_SELF: return "failed to get self"; + case SNUK_INTERP_ERR_SET_ENV_FAIL: return "failed to set env value"; + case SNUK_INTERP_ERR_MEMBER: return "couldn't find the member"; + case SNUK_INTERP_ERR_INTERFACE: return "failed to create interface"; + case SNUK_INTERP_ERR_TYPE_MISMATCH: return "types are not the same"; + } + return "unknown error"; +} From 2e175f5b3de779239a89bc7f48595c0221e31ebe Mon Sep 17 00:00:00 2001 From: kshku <1211shree@gmail.com> Date: Mon, 8 Jun 2026 12:43:47 +0530 Subject: [PATCH 2/8] feat: update parser to use SnukError --- include/snuk/parser/parser.h | 18 +++++++++++++---- include/snuk/parser/parser_common.h | 12 +++++++---- include/snuk/parser/snuk_item.h | 12 +++++------ src/parser/parser.c | 31 ++++++++++++++++++++++------- src/parser/snuk_expr.c | 20 +++++++++---------- src/parser/snuk_item.c | 4 ++-- src/parser/snuk_type.c | 8 ++++---- 7 files changed, 67 insertions(+), 38 deletions(-) diff --git a/include/snuk/parser/parser.h b/include/snuk/parser/parser.h index 2ae3886..6121bec 100644 --- a/include/snuk/parser/parser.h +++ b/include/snuk/parser/parser.h @@ -2,6 +2,7 @@ #include "snuk/defines.h" #include "snuk/lexer.h" +#include "snuk/snuk_error.h" /* * We will have items similar to rust. @@ -32,8 +33,7 @@ typedef struct SnukParser { SnukAllocator *allocator; bool panic_mode; /**< Error and recovery state flags. */ - const char *err_msg; - SnukToken err_token; + SnukError err; } SnukParser; /** @@ -74,9 +74,19 @@ SNUK_API SnukItem *snuk_parser_next_item(SnukParser *parser); * @brief Report a parser error and enter panic mode. * * @param parser Parser context to operate on. - * @param err_msg Error message to print. + * @param code Parse error code. + * @param msg Error message to print. */ -void parser_error(SnukParser *parser, const char *err_msg); +void parser_error(SnukParser *parser, SnukParseError code, const char *msg); + +/** + * @brief Clear the parser error state. + * + * @param parser Parser context to operate on. + * + * @return Previous error value. + */ +SnukError snuk_parser_clear_error(SnukParser *parser); /** * @brief Recover parser state after an error. diff --git a/include/snuk/parser/parser_common.h b/include/snuk/parser/parser_common.h index 71731e5..5fa2adb 100644 --- a/include/snuk/parser/parser_common.h +++ b/include/snuk/parser/parser_common.h @@ -17,7 +17,7 @@ typedef struct SnukVar SnukVar; SNUK_INLINE void parser_advance(SnukParser *parser) { parser->previous = parser->current; parser->current = parser->next; - if (parser->current.type == SNUK_TOKEN_ERROR) parser_error(parser, "lexer error"); + if (parser->current.type == SNUK_TOKEN_ERROR) parser_error(parser, SNUK_PARSE_ERR_LEXER_ERROR, "lexer error"); parser->next = snuk_lexer_next_token(&parser->lexer); } @@ -68,8 +68,12 @@ SNUK_INLINE bool parser_match(SnukParser *parser, SnukTokenType expected) { * @param expected Expected token type. * @param err_msg Error message to report if the token does not match. */ -SNUK_INLINE void parser_expect(SnukParser *parser, SnukTokenType expected, const char *err_msg) { - if (!parser_match(parser, expected)) parser_error(parser, err_msg); +SNUK_INLINE bool parser_expect(SnukParser *parser, SnukTokenType expected, const char *err_msg) { + if (!parser_match(parser, expected)) { + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, err_msg); + return false; + } + return true; } SNUK_INLINE bool parser_check_item_end(SnukParser *parser) { @@ -97,7 +101,7 @@ SNUK_INLINE bool parser_match_item_end(SnukParser *parser) { * @param parser Parser context to operate on. */ SNUK_INLINE void parser_expect_item_end(SnukParser *parser) { - if (!parser_match_item_end(parser)) parser_error(parser, "expected a new line or a semicolon"); + if (!parser_match_item_end(parser)) parser_error(parser, SNUK_PARSE_ERR_EXPECTED_SEMICOLON_OR_NEWLINE, "expected a new line or a semicolon"); } SNUK_INLINE SnukStringView parser_copy_string_view(SnukParser *parser, SnukStringView sv) { diff --git a/include/snuk/parser/snuk_item.h b/include/snuk/parser/snuk_item.h index 4527cf0..ddcf1ef 100644 --- a/include/snuk/parser/snuk_item.h +++ b/include/snuk/parser/snuk_item.h @@ -1,6 +1,7 @@ #pragma once #include "parser_common.h" +#include "snuk/snuk_error.h" #include "snuk/darray.h" #include "snuk/defines.h" #include "snuk/string_view.h" @@ -57,8 +58,7 @@ struct SnukItem { } interface_item; struct { - const char *msg; - SnukToken token; + SnukError error; } error; }; }; @@ -215,18 +215,16 @@ SNUK_INLINE SnukItem *build_interface_item(SnukParser *parser, SnukStringView na * @brief Build a error item. * * @param parser Parser context to operate on. - * @param msg The message - * @param token The token + * @param error The error * * @return Return error item. */ -SNUK_INLINE SnukItem *build_error_item(SnukParser *parser, const char *msg, SnukToken token) { +SNUK_INLINE SnukItem *build_error_item(SnukParser *parser, SnukError error) { SnukItem *item = parser_create_item(parser); *item = (SnukItem){ .type = SNUK_ITEM_ERROR, .error = { - .msg = msg, - .token = token, + .error = error, }, }; return item; diff --git a/src/parser/parser.c b/src/parser/parser.c index 2ec2a6d..4bd698a 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -12,30 +12,47 @@ void snuk_parser_init(SnukParser *parser, const char *src, SnukAllocator *alloca parser->previous = (SnukToken){0}; parser->current = snuk_lexer_next_token(&parser->lexer); - if (parser->current.type == SNUK_TOKEN_ERROR) parser_error(parser, "lexer error"); + if (parser->current.type == SNUK_TOKEN_ERROR) parser_error(parser, SNUK_PARSE_ERR_LEXER_ERROR, "lexer error"); parser->next = snuk_lexer_next_token(&parser->lexer); } +SnukError snuk_parser_clear_error(SnukParser *parser) { + SnukError err = parser->err; + parser->err = SNUK_ERROR_NONE; + return err; +} + void snuk_parser_deinit(SnukParser *parser) { if (!parser) return; snuk_lexer_deinit(&parser->lexer); *parser = (SnukParser){0}; } -void parser_error(SnukParser *parser, const char *err_msg) { +void parser_error(SnukParser *parser, SnukParseError code, const char *msg) { if (parser->panic_mode) return; parser->panic_mode = true; - parser->err_msg = err_msg; - parser->err_token = parser->current; + parser->err = (SnukError){ + .kind = SNUK_ERROR_KIND_PARSE, + .code = code, + .msg = msg, + .loc = { + .line = parser->current.line, + .col = parser->current.col, + }, + }; } SnukItem *parser_sync(SnukParser *parser) { + while (parser->current.type != SNUK_TOKEN_EOF + && parser->current.type != SNUK_TOKEN_SEMICOLON + && parser->current.type != SNUK_TOKEN_VSEMICOLON) { + parser_advance(parser); + } + SnukItem *item = build_error_item(parser, parser->err); parser->panic_mode = false; - if (parser->previous.type != SNUK_TOKEN_VSEMICOLON && parser->previous.type != SNUK_TOKEN_SEMICOLON) - while (!parser_match_item_end(parser)) parser_advance(parser); - return build_error_item(parser, parser->err_msg, parser->err_token); + return item; } SnukItem *snuk_parser_next_item(SnukParser *parser) { diff --git a/src/parser/snuk_expr.c b/src/parser/snuk_expr.c index 74f0ed2..b8b7620 100644 --- a/src/parser/snuk_expr.c +++ b/src/parser/snuk_expr.c @@ -360,7 +360,7 @@ static SnukExpr *parse_precedence(SnukParser *parser, Precedence precedence) { parser_advance(parser); prefix_fn pfn = get_rule(parser->previous.type)->pfn; if (!pfn) { - parser_error(parser, "expected expression"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected expression"); return NULL; } @@ -398,7 +398,7 @@ static SnukExpr *parse_primary(SnukParser *parser) { break; default: // TODO: - parser_error(parser, "unexpected expression"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "unexpected expression"); break; } @@ -431,7 +431,7 @@ static SnukExpr *parse_assignment(SnukParser *parser, SnukExpr *left) { static SnukExpr *parse_compound_assignment(SnukParser *parser, SnukExpr *left) { if (left->type != SNUK_EXPR_IDENTIFIER && left->type != SNUK_EXPR_MEMBER) { - parser_error(parser, "invalid assignment target"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "invalid assignment target"); return NULL; } SnukTokenType op = parser->previous.type; @@ -568,7 +568,7 @@ static SnukExpr *parse_fn(SnukParser *parser) { } if (parser->previous.type != SNUK_TOKEN_RPAREN) { - parser_error(parser, "expected ')'"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected ')'"); return NULL; } @@ -594,7 +594,7 @@ static SnukExpr *parse_call(SnukParser *parser, SnukExpr *left) { } if (parser->previous.type != SNUK_TOKEN_RPAREN) { - parser_error(parser, "expected ')'"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected ')'"); return NULL; } return build_call_expr(parser, left, params); @@ -625,7 +625,7 @@ static SnukExpr *parse_list(SnukParser *parser) { } if (parser->previous.type != SNUK_TOKEN_RBRACKET) { - parser_error(parser, "expected ']' after list elements"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected ']' after list elements"); return NULL; } @@ -644,12 +644,12 @@ static SnukExpr *parse_type(SnukParser *parser, SnukStringView name) { SnukItem *item = snuk_item_parse(parser); snuk_darray_push(&members, item); } else { - parser_error(parser, "unexpected token"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "unexpected token"); } } if (parser->previous.type != SNUK_TOKEN_RBRACE) { - parser_error(parser, "expected '}'"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected '}'"); return NULL; } @@ -677,7 +677,7 @@ static SnukExpr *parse_type_inst(SnukParser *parser, SnukType *type) { } if (parser->previous.type != SNUK_TOKEN_RBRACE) { - parser_error(parser, "expected '}'"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected '}'"); return NULL; } @@ -706,7 +706,7 @@ static SnukExpr *parse_block(SnukParser *parser) { block_expr = build_block_expr(parser, block_expr, snuk_item_parse(parser)); if (parser->previous.type != SNUK_TOKEN_RBRACE) { - parser_error(parser, "block was not closed"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "block was not closed"); return NULL; } diff --git a/src/parser/snuk_item.c b/src/parser/snuk_item.c index e4c748e..5d6e73f 100644 --- a/src/parser/snuk_item.c +++ b/src/parser/snuk_item.c @@ -129,12 +129,12 @@ static SnukItem *parse_extend_item(SnukParser *parser) { SnukItem *item = snuk_item_parse(parser); extend_item = build_extend_item(parser, extend_item, NULL, item); } else { - parser_error(parser, "unexpected token"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "unexpected token"); } } if (parser->previous.type != SNUK_TOKEN_RBRACE) { - parser_error(parser, "expected '}'"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected '}'"); return NULL; } diff --git a/src/parser/snuk_type.c b/src/parser/snuk_type.c index 374c764..3fab5ca 100644 --- a/src/parser/snuk_type.c +++ b/src/parser/snuk_type.c @@ -16,18 +16,18 @@ SnukType *snuk_type_parse_interface(SnukParser *parser) { while (!parser_match(parser, SNUK_TOKEN_RBRACE) && parser->current.type != SNUK_TOKEN_EOF) { if (!parser_match(parser, SNUK_TOKEN_VAR) && !parser_match(parser, SNUK_TOKEN_CONST)) { - parser_error(parser, "expected var or const"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected var or const"); return NULL; } SnukVar *var = snuk_var_parse(parser, false); parser_expect_item_end(parser); - if (var->value) parser_error(parser, "interface members should not have values"); + if (var->value) parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "interface members should not have values"); type = build_interface_type(parser, type, var); } if (parser->previous.type != SNUK_TOKEN_RBRACE) { - parser_error(parser, "expected '}'"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected '}'"); return NULL; } @@ -46,7 +46,7 @@ SnukType *snuk_type_parse(SnukParser *parser) { } if (parser->previous.type != SNUK_TOKEN_RPAREN) { - parser_error(parser, "expected ')'"); + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "expected ')'"); return NULL; } From 2c3f5942249fcace1fe89be362cf0390da5c4ef7 Mon Sep 17 00:00:00 2001 From: kshku <1211shree@gmail.com> Date: Mon, 8 Jun 2026 12:44:03 +0530 Subject: [PATCH 3/8] chore: fix naming conflict with SNUK_ERROR_NONE, update error item field access --- include/snuk/interpreter/error_code.h | 2 +- include/snuk/interpreter/interpreter_helper.h | 2 +- src/interpreter/error_code.c | 2 +- src/interpreter/interpreter.c | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/snuk/interpreter/error_code.h b/include/snuk/interpreter/error_code.h index 3b5c78a..6dc6f20 100644 --- a/include/snuk/interpreter/error_code.h +++ b/include/snuk/interpreter/error_code.h @@ -3,7 +3,7 @@ #include "snuk/defines.h" typedef enum SnukErrorCode { - SNUK_ERROR_NONE = 0, + SNUK_ERROR_CODE_NONE = 0, SNUK_ERROR_SHOULD_NOT_REACH_HERE, SNUK_ERROR_SOMETHING_WENT_WRONG, SNUK_ERROR_CONTROL_FLOW, diff --git a/include/snuk/interpreter/interpreter_helper.h b/include/snuk/interpreter/interpreter_helper.h index d1b4eae..439030d 100644 --- a/include/snuk/interpreter/interpreter_helper.h +++ b/include/snuk/interpreter/interpreter_helper.h @@ -12,7 +12,7 @@ SnukValue execute_block_expr( SnukValue interpreter_copy_inst(SnukInterpreter *intpret, SnukValue inst); SNUK_INLINE void interpreter_error(SnukInterpreter *intpret, SnukErrorCode err_code) { - if (intpret->err_code != SNUK_ERROR_NONE) return; + if (intpret->err_code != SNUK_ERROR_CODE_NONE) return; intpret->err_code = err_code; } diff --git a/src/interpreter/error_code.c b/src/interpreter/error_code.c index 998c9bf..043b40a 100644 --- a/src/interpreter/error_code.c +++ b/src/interpreter/error_code.c @@ -1,7 +1,7 @@ #include "snuk/interpreter/error_code.h" static const char *error_messages[] = { - [SNUK_ERROR_NONE] = "All is well", + [SNUK_ERROR_CODE_NONE] = "All is well", [SNUK_ERROR_SHOULD_NOT_REACH_HERE] = "Shouldn't reach here", [SNUK_ERROR_SOMETHING_WENT_WRONG] = "Something went wrong", [SNUK_ERROR_CONTROL_FLOW] = "control flow item outside scope", diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index c2ff1de..7022bbd 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -65,7 +65,7 @@ void snuk_interpreter_init(SnukInterpreter *intpret) { .realloc = realloc_fn, .free = free_fn, }, - .err_code = SNUK_ERROR_NONE, + .err_code = SNUK_ERROR_CODE_NONE, }; sn_linear_allocator_init(&intpret->la, intpret->mem, PAGES * snuk_page_size()); intpret->current = snuk_ref_counter_retain(intpret->global); @@ -209,7 +209,7 @@ SnukValue snuk_interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item) { if (intpret->signal != SNUK_SIGNAL_NONE) interpreter_error(intpret, SNUK_ERROR_CONTROL_FLOW); res.err_code = intpret->err_code; - intpret->err_code = SNUK_ERROR_NONE; + intpret->err_code = SNUK_ERROR_CODE_NONE; return res; } @@ -1148,7 +1148,7 @@ static SnukValue interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item, return execute_interface(intpret, item, weak_ref); case SNUK_ITEM_ERROR: - log_error("Error: %s", item->error.msg); + log_error("Error: %s", item->error.error.msg); return (SnukValue){.type = SNUK_VALUE_NULL}; case SNUK_ITEM_MAX: From 3590109c817d5140f903c72d2f47988a9ddfe601 Mon Sep 17 00:00:00 2001 From: kshku <1211shree@gmail.com> Date: Mon, 8 Jun 2026 13:02:59 +0530 Subject: [PATCH 4/8] feat: update interpreter to use SnukError --- .../plans/2025-01-01-error-reporting-plan.md | 660 ++++++++++++++++++ .../2025-01-01-error-reporting-design.md | 129 ++++ include/snuk/interpreter/error_code.h | 31 - include/snuk/interpreter/interpreter.h | 8 +- include/snuk/interpreter/interpreter_helper.h | 18 +- include/snuk/interpreter/snuk_value.h | 2 - repl/runtime.c | 7 +- src/interpreter/CMakeLists.txt | 2 - src/interpreter/error_code.c | 29 - src/interpreter/interpreter.c | 96 +-- 10 files changed, 866 insertions(+), 116 deletions(-) create mode 100644 docs/superpowers/plans/2025-01-01-error-reporting-plan.md create mode 100644 docs/superpowers/specs/2025-01-01-error-reporting-design.md delete mode 100644 include/snuk/interpreter/error_code.h delete mode 100644 src/interpreter/error_code.c diff --git a/docs/superpowers/plans/2025-01-01-error-reporting-plan.md b/docs/superpowers/plans/2025-01-01-error-reporting-plan.md new file mode 100644 index 0000000..1175aee --- /dev/null +++ b/docs/superpowers/plans/2025-01-01-error-reporting-plan.md @@ -0,0 +1,660 @@ +# Error Reporting Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the current ad-hoc error reporting with a clean library-produces-data, consumer-formats architecture. + +**Architecture:** A single `SnukError` POD type shared by parser and interpreter. Both produce structured errors with source location (line/col). The REPL/consumer formats and prints. The library never prints. + +**Tech Stack:** C17, CMake, existing test infrastructure + +--- + +## File Structure + +- **Create:** `include/snuk/snuk_error.h` — `SnukSrcLoc`, `SnukErrorKind`, `SnukError`, runtime/parse error code enums +- **Modify:** `include/snuk/interpreter/interpreter.h` — replace `SnukErrorCode err_code` with `SnukError err` + `SnukSrcLoc cur_loc` +- **Modify:** `include/snuk/interpreter/interpreter_helper.h` — update `interpreter_error()`, add `interpreter_set_loc()` +- **Modify:** `src/interpreter/interpreter.c` — update all error calls, add loc tracking at expression entry points +- **Modify:** `include/snuk/parser/parser.h` — replace `err_msg`/`err_token` with `SnukError err` +- **Modify:** `include/snuk/parser/snuk_item.h` — replace `const char *msg` with `SnukError error` in error item +- **Modify:** `src/parser/parser.c` — update `parser_error()`, `parser_sync()` +- **Modify:** `src/parser/parser_common.h` — update `parser_advance()`, `parser_expect()` +- **Modify:** `repl/runtime.c` — format `SnukError` for display +- **Remove:** `include/snuk/interpreter/error_code.h` — replaced by `snuk_error.h` +- **Remove:** `src/interpreter/error_code.c` — replaced by `snuk_error.h` +- **Remove:** `repl/runtime.h` — inline functions (no changes needed but verify) + +--- + +### Task 1: Create `snuk_error.h` with core types + +**Files:** +- Create: `include/snuk/snuk_error.h` +- Modify: None + +- [ ] **Step 1: Write the header file** + +```c +#pragma once + +#include + +typedef struct { + const char *file; // filename or NULL + uint32_t line; // 1-indexed + uint32_t col; // 1-indexed +} SnukSrcLoc; + +#define SNUK_SRC_LOC_NULL ((SnukSrcLoc){NULL, 0, 0}) + +typedef enum { + SNUK_ERROR_KIND_NONE = 0, + SNUK_ERROR_KIND_PARSE, + SNUK_ERROR_KIND_INTERP, +} SnukErrorKind; + +typedef enum { + SNUK_INTERP_ERR_NONE = 0, + SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, + SNUK_INTERP_ERR_SOMETHING_WENT_WRONG, + SNUK_INTERP_ERR_CONTROL_FLOW, + SNUK_INTERP_ERR_EXISTS, + SNUK_INTERP_ERR_NON_TYPE, + SNUK_INTERP_ERR_EXPECT_ASSIGN, + SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE, + SNUK_INTERP_ERR_MEMBER_INITIALIZE, + SNUK_INTERP_ERR_SELF_CREATION, + SNUK_INTERP_ERR_PARAM_CREATION, + SNUK_INTERP_ERR_NON_FN, + SNUK_INTERP_ERR_PARAM_COUNT, + SNUK_INTERP_ERR_NO_PARAM, + SNUK_INTERP_ERR_PARAM_MIXED, + SNUK_INTERP_ERR_PARAM_REQUIRED, + SNUK_INTERP_ERR_SELF, + SNUK_INTERP_ERR_SET_ENV_FAIL, + SNUK_INTERP_ERR_MEMBER, + SNUK_INTERP_ERR_INTERFACE, + SNUK_INTERP_ERR_TYPE_MISMATCH, +} SnukInterpError; + +typedef enum { + SNUK_PARSE_ERR_NONE = 0, + SNUK_PARSE_ERR_UNEXPECTED_TOKEN, + SNUK_PARSE_ERR_EXPECTED_IDENTIFIER, + SNUK_PARSE_ERR_EXPECTED_TYPE_ANNOTATION, + SNUK_PARSE_ERR_EXPECTED_SEMICOLON_OR_NEWLINE, + SNUK_PARSE_ERR_EXPECTED_CLOSE_BRACE, + SNUK_PARSE_ERR_EXPECTED_CLOSE_PAREN, + SNUK_PARSE_ERR_EXPECTED_CLOSE_BRACKET, + SNUK_PARSE_ERR_EXPECTED_KEYWORD, + SNUK_PARSE_ERR_EXPECTED_EXPRESSION, + SNUK_PARSE_ERR_EXPECTED_TYPE, + SNUK_PARSE_ERR_EXPECTED_MEMBER, + SNUK_PARSE_ERR_INVALID_LITERAL, + SNUK_PARSE_ERR_UNTERMINATED_STRING, + SNUK_PARSE_ERR_LEXER_ERROR, + SNUK_PARSE_ERR_UNKNOWN, +} SnukParseError; + +typedef struct { + SnukErrorKind kind; + uint32_t code; // cast from SnukInterpError or SnukParseError + const char *msg; // human-readable description + SnukSrcLoc loc; +} SnukError; + +#define SNUK_ERROR_NONE ((SnukError){SNUK_ERROR_KIND_NONE, 0, NULL, SNUK_SRC_LOC_NULL}) + +static inline const char *snuk_interp_error_msg(SnukInterpError code) { + switch (code) { + case SNUK_INTERP_ERR_NONE: return "no error"; + case SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE: return "shouldn't reach here"; + case SNUK_INTERP_ERR_SOMETHING_WENT_WRONG: return "something went wrong"; + case SNUK_INTERP_ERR_CONTROL_FLOW: return "control flow item outside scope"; + case SNUK_INTERP_ERR_EXISTS: return "variable already exists"; + case SNUK_INTERP_ERR_NON_TYPE: return "expected a type"; + case SNUK_INTERP_ERR_EXPECT_ASSIGN: return "expected assignment expression"; + case SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE: return "invalid value for builtin type member"; + case SNUK_INTERP_ERR_MEMBER_INITIALIZE: return "failed to initialize member"; + case SNUK_INTERP_ERR_SELF_CREATION: return "failed to create self"; + case SNUK_INTERP_ERR_PARAM_CREATION: return "failed to create parameter"; + case SNUK_INTERP_ERR_NON_FN: return "call expression on non-function"; + case SNUK_INTERP_ERR_PARAM_COUNT: return "parameter count mismatch"; + case SNUK_INTERP_ERR_NO_PARAM: return "parameter doesn't exist"; + case SNUK_INTERP_ERR_PARAM_MIXED: return "mixed positional and named parameters"; + case SNUK_INTERP_ERR_PARAM_REQUIRED: return "required parameter missing"; + case SNUK_INTERP_ERR_SELF: return "failed to get self"; + case SNUK_INTERP_ERR_SET_ENV_FAIL: return "failed to set env value"; + case SNUK_INTERP_ERR_MEMBER: return "couldn't find the member"; + case SNUK_INTERP_ERR_INTERFACE: return "failed to create interface"; + case SNUK_INTERP_ERR_TYPE_MISMATCH: return "types are not the same"; + } + return "unknown error"; +} +``` + +- [ ] **Step 2: Verify it compiles in isolation** + +Run: `echo '#include "snuk_error.h"' | gcc -std=c17 -fsyntax-only -I include -x c -` +Expected: no errors + +- [ ] **Step 3: Commit** + +```bash +git add include/snuk/snuk_error.h +git commit -m "feat: add SnukError types for structured error reporting" +``` + +--- + +### Task 2: Update parser to use SnukError + +**Files:** +- Modify: `include/snuk/parser/parser.h` +- Modify: `include/snuk/parser/snuk_item.h` +- Modify: `src/parser/parser.c` +- Modify: `src/parser/parser_common.h` + +- [ ] **Step 1: Update `include/snuk/parser/parser.h`** + +Replace `err_msg`/`err_token` with a single `SnukError err`: + +```c +#include "snuk/snuk_error.h" + +typedef struct SnukParser { + SnukLexer *lexer; + SnukToken previous; + SnukToken current; + SnukToken next; + bool panic_mode; + SnukError err; // <-- replace err_msg + err_token + struct { + char *buffer; + uint64_t length; + } string_buffer; +} SnukParser; +``` + +Also add `snuk_parser_clear_error()` declaration: + +```c +SnukError snuk_parser_clear_error(SnukParser *parser); +``` + +- [ ] **Step 2: Update `include/snuk/parser/snuk_item.h`** + +Replace the error item's `const char *msg` with `SnukError error`: + +```c +#include "snuk/snuk_error.h" + +// Inside the item union: +struct { + SnukError error; // <-- was const char *msg + SnukToken token +} error; +``` + +Remove `build_error_item()` and replace with a version that takes `SnukError`: + +```c +SnukItem *snuk_build_error_item(SnukAllocator allocator, SnukError error); +``` + +- [ ] **Step 3: Update `src/parser/parser.c`** + +Replace `parser_error()`: + +```c +#include "snuk/lexer.h" + +void parser_error(SnukParser *parser, SnukParseError code, const char *msg) { + if (parser->panic_mode) return; + parser->panic_mode = true; + parser->err = (SnukError){ + .kind = SNUK_ERROR_KIND_PARSE, + .code = code, + .msg = msg, + .loc = { + .line = parser->current.line, + .col = parser->current.col, + }, + }; +} +``` + +Update `parser_sync()`: + +```c +static SnukItem *parser_sync(SnukParser *parser) { + while (parser->current.type != SNUK_TOKEN_EOF + && parser->current.type != SNUK_TOKEN_SEMICOLON + && parser->current.type != SNUK_TOKEN_NEWLINE) { + parser_advance(parser); + } + SnukItem *item = snuk_build_error_item(parser->allocator, parser->err); + parser->panic_mode = false; + return item; +} +``` + +- [ ] **Step 4: Update `src/parser/parser_common.h`** + +Update `parser_advance()`: + +```c +#define parser_advance(parser) \ + do { \ + (parser)->previous = (parser)->current; \ + (parser)->current = (parser)->next; \ + (parser)->next = snuk_lexer_next_token((parser)->lexer); \ + if ((parser)->current.type == SNUK_TOKEN_ERROR) \ + parser_error(parser, SNUK_PARSE_ERR_LEXER_ERROR, "lexer error"); \ + } while (0) +``` + +Update `parser_expect()`: + +```c +static inline bool parser_expect(SnukParser *parser, SnukTokenType type, const char *msg) { + if (parser->current.type == type) { + parser_advance(parser); + return true; + } + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, msg); + return false; +} +``` + +- [ ] **Step 5: Update `snuk_build_error_item()` in snuk_item.h** + +```c +SnukItem *snuk_build_error_item(SnukAllocator allocator, SnukError error) { + SnukItem *item = snuk_alloc_item(allocator); + if (!item) return NULL; + item->type = SNUK_ITEM_ERROR; + item->error.error = error; + return item; +} +``` + +- [ ] **Step 6: Update all parser .c files that call `parser_error()`** + +These files currently call `parser_error(parser, "some string")`. Update them all: + +```c +// Old: +parser_error(parser, "expected identifier"); + +// New: +parser_error(parser, SNUK_PARSE_ERR_EXPECTED_IDENTIFIER, "expected identifier"); +``` + +Files to update (grep for `parser_error`): +- `src/parser/snuk_expr.c` +- `src/parser/snuk_item.c` +- `src/parser/snuk_type.c` + +- [ ] **Step 7: Build and verify parser compiles** + +Run: `cmake --build build --target snuk_repl -j$(nproc) 2>&1` +Expected: compiles with no errors + +- [ ] **Step 8: Run existing .snuk tests to verify parser still works** + +Run: `ctest --test-dir build/tests -L snuk_files --output-on-failure` +Expected: all pass + +- [ ] **Step 9: Commit** + +```bash +git add include/snuk/parser/parser.h include/snuk/parser/snuk_item.h src/parser/ +git commit -m "feat: update parser to use SnukError" +``` + +--- + +### Task 3: Update interpreter to use SnukError + +**Files:** +- Modify: `include/snuk/interpreter/interpreter.h` +- Modify: `include/snuk/interpreter/interpreter_helper.h` +- Modify: `include/snuk/interpreter/snuk_value.h` +- Modify: `src/interpreter/interpreter.c` +- Remove: `include/snuk/interpreter/error_code.h` +- Remove: `src/interpreter/error_code.c` + +- [ ] **Step 1: Update `include/snuk/interpreter/interpreter.h`** + +Replace `SnukErrorCode err_code` with `SnukError err` + `SnukSrcLoc cur_loc`: + +```c +#include "snuk/snuk_error.h" + +typedef struct SnukInterpreter { + SnukRefCounter *global; + SnukRefCounter *current; + SnukRefCounter *instance; + + SnukError err; // <-- replaces SnukErrorCode err_code + SnukSrcLoc cur_loc; // current source location for error reporting + + SnukDarray /* SnukValue * */ trash; + SnukSignal signal; + // ... +} SnukInterpreter; +``` + +Remove `#include "error_code.h"` from the includes. + +Add declarations: +```c +SnukError snuk_interpreter_clear_error(SnukInterpreter *intpret); +void snuk_interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col); +``` + +- [ ] **Step 2: Update `include/snuk/interpreter/snuk_value.h`** + +Replace `SnukErrorCode err_code` with a cleaner approach — keep for now but rename to avoid confusion. Actually, the simplest approach: keep `SnukErrorCode err_code` field for now but typedef `SnukErrorCode` as a generic uint32_t. Or better, just remove it from the value and have the error be on the interpreter only. + +Actually, the current system copies `err_code` from the interpreter onto the returned value to communicate errors. With the new system, the consumer calls `snuk_interpreter_clear_error()` after `exec_item()`. So we don't need `err_code` on the value anymore. + +Remove `SnukErrorCode err_code;` from the `SnukValue` struct. Adjust all references. + +- [ ] **Step 3: Update `include/snuk/interpreter/interpreter_helper.h`** + +Update `interpreter_error()`: + +```c +#include "snuk/snuk_error.h" + +static inline void interpreter_error(SnukInterpreter *intpret, SnukInterpError code, const char *msg) { + if (intpret->err.kind != SNUK_ERROR_KIND_NONE) return; // first error wins + intpret->err = (SnukError){ + .kind = SNUK_ERROR_KIND_INTERP, + .code = code, + .msg = msg, + .loc = intpret->cur_loc, + }; +} +``` + +Add `interpreter_set_loc()`: + +```c +static inline void interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col) { + intpret->cur_loc.line = line; + intpret->cur_loc.col = col; +} +``` + +- [ ] **Step 4: Update `src/interpreter/interpreter.c`** + +Update all `interpreter_error(intpret, SNUK_ERROR_XXX)` calls to `interpreter_error(intpret, SNUK_INTERP_ERR_XXX, msg)`. + +Update `snuk_interpreter_exec_item()`: + +```c +SnukValue snuk_interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item) { + interpreter_clear_trash(intpret); + + // Set cur_loc from item if available (parser items track line/col) + // For now, we just clear the error at the start of each item + SnukValue res = interpreter_exec_item(intpret, item, true); + + if (intpret->signal != SNUK_SIGNAL_NONE) { + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); + } + + // Error is now on intpret->err, not copied to value + // Consumer reads via snuk_interpreter_clear_error() + return res; +} +``` + +Add `snuk_interpreter_clear_error()`: + +```c +SnukError snuk_interpreter_clear_error(SnukInterpreter *intpret) { + SnukError err = intpret->err; + intpret->err = (SnukError)SNUK_ERROR_NONE; + return err; +} + +void snuk_interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col) { + interpreter_set_loc(intpret, line, col); +} +``` + +Update all `interpreter_error(ptr, SNUK_ERROR_*)` calls throughout the file: + +```c +// Old: +interpreter_error(intpret, SNUK_ERROR_NON_FN); + +// New: +interpreter_error(intpret, SNUK_INTERP_ERR_NON_FN, "call expression on non-function"); +``` + +Grep for all `SNUK_ERROR_` references in the file and replace: +- `SNUK_ERROR_NONE` → check `intpret->err.kind == SNUK_ERROR_KIND_NONE` +- `SNUK_ERROR_CONTROL_FLOW` → `SNUK_INTERP_ERR_CONTROL_FLOW` +- `SNUK_ERROR_EXISTS` → `SNUK_INTERP_ERR_EXISTS` +- etc. + +The only error guard in `execute_call_expr()` (around line 1029): + +```c +if (intpret->err.kind != SNUK_ERROR_KIND_NONE) { + snuk_ref_counter_release(&new_scope); + return (SnukValue){.type = SNUK_VALUE_UNKOWN}; +} +``` + +- [ ] **Step 5: Remove old error files** + +Delete `include/snuk/interpreter/error_code.h` and `src/interpreter/error_code.c`. +Remove the `error_code.c` source from `CMakeLists.txt` in the interpreter directory. + +- [ ] **Step 6: Build and verify** + +Run: `cmake --build build --target snuk_repl -j$(nproc) 2>&1` +Expected: compiles with no errors + +- [ ] **Step 7: Run tests** + +Run: `ctest --test-dir build/tests -L snuk_files --output-on-failure` +Expected: all pass (some tests may need regex update — see Task 5) + +- [ ] **Step 8: Commit** + +```bash +git add include/snuk/interpreter/ src/interpreter/ +git rm include/snuk/interpreter/error_code.h src/interpreter/error_code.c +git commit -m "feat: update interpreter to use SnukError" +``` + +--- + +### Task 4: Thread source location through interpreter + +**Files:** +- Modify: `src/interpreter/interpreter.c` + +- [ ] **Step 1: Add `interpreter_set_loc()` calls at expression entry points** + +In each `execute_*` function, set the loc at entry. Since we don't have loc on every AST node yet, use a simple approach: the parser sets line/col from the item's token when calling `snuk_interpreter_exec_item()`, and each expression evaluator updates it. + +For now, update `snuk_interpreter_exec_item()` to accept an optional start location and set it on the interpreter: + +```c +SnukValue snuk_interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item, SnukSrcLoc start_loc) { + interpreter_clear_trash(intpret); + intpret->cur_loc = start_loc; + // ... rest of function +} +``` + +But wait — changing the signature breaks the public API. Better to have a separate setter called by the runtime before exec_item: + +```c +// In runtime.c: +snuk_interpreter_set_loc(&rt->interpreter, line, col); +SnukValue val = snuk_interpreter_exec_item(&rt->interpreter, item); +``` + +The parser can't easily provide line/col per item right now (the token info is in the parser, not on the item). For the initial implementation, just set loc to (0,0) or the last-known token position from the parser. + +- [ ] **Step 2: Build and test** + +Run: `cmake --build build --target snuk_repl -j$(nproc) && ctest --test-dir build/tests -L snuk_files --output-on-failure` +Expected: all pass + +- [ ] **Step 3: Commit** + +```bash +git add src/interpreter/interpreter.c include/snuk/interpreter/interpreter.h +git commit -m "feat: add cur_loc tracking to interpreter" +``` + +--- + +### Task 5: Update runtime/REPL to display errors + +**Files:** +- Modify: `repl/runtime.c` +- Modify: `CMakeLists.txt` (if needed for test regex) + +- [ ] **Step 1: Update `repl/runtime.c`** + +Replace the error display: + +```c +#include + +void snuk_runtime_execute(Runtime *rt, const char *src) { + SnukParser parser; + snuk_parser_init(&parser, src, &rt->parser_allocator); + + SnukItem *item; + while (true) { + item = snuk_parser_next_item(&parser); + if (!item) break; + + // Handle parser errors + SnukError parse_err = snuk_parser_clear_error(&parser); + if (parse_err.kind != SNUK_ERROR_KIND_NONE) { + snuk_println("[Error] at line %u, col %u: %s", + parse_err.loc.line, parse_err.loc.col, parse_err.msg); + continue; + } + + SnukValue value = snuk_interpreter_exec_item(&rt->interpreter, item); + + // Handle interpreter errors + SnukError interp_err = snuk_interpreter_clear_error(&rt->interpreter); + if (interp_err.kind != SNUK_ERROR_KIND_NONE) { + snuk_println("[Error] at line %u, col %u: %s", + interp_err.loc.line, interp_err.loc.col, interp_err.msg); + } + + snuk_value_log(value); + log_trace("", NULL); + snuk_value_free(value); + } + + snuk_parser_deinit(&parser); +} +``` + +- [ ] **Step 2: Update test regex in `tests/CMakeLists.txt`** + +The test regex checks for "ERROR|error". Our new format says "[Error]" which also matches. But let's verify: + +Run: `ctest --test-dir build/tests -L snuk_files --output-on-failure` +Expected: all pass + +If tests fail because error output format changed, update `FAIL_REGULAR_EXPRESSION` to match the new format. + +- [ ] **Step 3: Commit** + +```bash +git add repl/runtime.c tests/CMakeLists.txt +git commit -m "feat: update REPL to display structured SnukError" +``` + +--- + +### Task 6: Remove library-side log_error calls + +**Files:** +- Modify: `src/interpreter/interpreter.c` +- Modify: `src/parser/` (if any log_error calls exist) + +- [ ] **Step 1: Remove `log_error` calls from interpreter** + +The interpreter currently has one `log_error` call in the `SNUK_ITEM_ERROR` handler (line 1150-1152): + +```c +case SNUK_ITEM_ERROR: + log_error("Error: %s", item->error.msg); + return (SnukValue){.type = SNUK_VALUE_NULL}; +``` + +Replace with: + +```c +case SNUK_ITEM_ERROR: + // Error data is in item->error.error; consumer handles display + // Store parser error on interpreter for the consumer to read + if (intpret->err.kind == SNUK_ERROR_KIND_NONE) { + intpret->err = item->error.error; // propagate parser error through interpreter + } + return (SnukValue){.type = SNUK_VALUE_NULL}; +``` + +- [ ] **Step 2: Build and test** + +Run: `cmake --build build --target snuk_repl -j$(nproc) && ctest --test-dir build/tests -L snuk_files --output-on-failure` +Expected: all pass + +- [ ] **Step 3: Commit** + +```bash +git add src/interpreter/interpreter.c +git commit -m "feat: remove library-side log_error, propagate SnukError through interpreter" +``` + +--- + +### Task 7: Clean up includes and verify everything compiles + +**Files:** +- All modified files + +- [ ] **Step 1: Remove obsolete includes** + +Search for leftover `#include "error_code.h"` references and replace with `#include "snuk/snuk_error.h"`: + +```bash +grep -rn "error_code.h" include/ src/ repl/ --include="*.h" --include="*.c" +``` + +Also update any `error_code.h` includes that need to point to `snuk_error.h`. + +- [ ] **Step 2: Full rebuild and test** + +```bash +cmake --build build -j$(nproc) 2>&1 +ctest --test-dir build/tests --output-on-failure +``` + +Expected: all compile, all tests pass + +- [ ] **Step 3: Commit** + +```bash +git add -A +git commit -m "chore: clean up includes, remove old error_code references" +``` diff --git a/docs/superpowers/specs/2025-01-01-error-reporting-design.md b/docs/superpowers/specs/2025-01-01-error-reporting-design.md new file mode 100644 index 0000000..c720dde --- /dev/null +++ b/docs/superpowers/specs/2025-01-01-error-reporting-design.md @@ -0,0 +1,129 @@ +# Error Reporting Design + +## Goals + +- Library produces structured error data; the consumer (REPL/embedder) handles formatting and display +- Both parser and interpreter produce errors through the same type +- Errors carry source location (line, col) for user-friendly messages +- No formatting, printing, or I/O in the library — pure data + +## Data Types + +```c +typedef struct { + const char *file; // filename or NULL (unknown source) + uint32_t line; // 1-indexed + uint32_t col; // 1-indexed +} SnukSrcLoc; + +typedef enum { + SNUK_ERROR_KIND_PARSE, + SNUK_ERROR_KIND_RUNTIME, +} SnukErrorKind; + +typedef struct { + SnukErrorKind kind; + uint32_t code; // error code (e.g., TYPE_MISMATCH, PARAM_COUNT) + const char *msg; // human-readable description, no newlines + SnukSrcLoc loc; +} SnukError; +``` + +`SnukError` is POD — no allocation, returned by value, copied freely. + +## Error Codes + +Replace the current `SnukErrorCode` enum with separate enums for parse vs runtime errors: + +```c +// Parse errors +typedef enum { + SNUK_PARSE_ERR_NONE = 0, + SNUK_PARSE_ERR_UNEXPECTED_TOKEN, + SNUK_PARSE_ERR_EXPECTED_IDENTIFIER, + SNUK_PARSE_ERR_EXPECTED_SEMICOLON, + SNUK_PARSE_ERR_EXPECTED_CLOSE_BRACE, + // ... +} SnukParseErrorCode; + +// Runtime errors +typedef enum { + SNUK_RUNTIME_ERR_NONE = 0, + SNUK_RUNTIME_ERR_TYPE_MISMATCH, + SNUK_RUNTIME_ERR_NON_FUNCTION, + SNUK_RUNTIME_ERR_PARAM_COUNT, + SNUK_RUNTIME_ERR_UNDEFINED_VARIABLE, + SNUK_RUNTIME_ERR_DUPLICATE_VARIABLE, + // ... +} SnukRuntimeErrorCode; +``` + +## Parser Flow + +- `parser_error(parser, kind, msg)` → builds `SnukError` from the current token's line/col, stores it in the parser +- Parser continues producing `SNUK_ITEM_ERROR` items, but the error item carries a full `SnukError` instead of a raw string +- Consumer reads `snuk_parser_clear_error(parser)` after each `next_item()` call + +## Interpreter Flow + +- The interpreter struct gets a `SnukError err` field (one-shot latch, same pattern as current `err_code`) +- `interpreter_error(intpret, kind, msg, loc)` → sets `intpret->err` if no error is already latched +- Location tracking: + - The interpreter has a single `SnukSrcLoc cur_loc` field, updated before each expression is evaluated + - Each `execute_*` function calls `interpreter_set_loc(intpret, line, col)` before evaluating + - The parser already computes line/col for each token; pass this through to the interpreter + - NO changes to AST nodes needed — loc is tracked in the interpreter, not on expressions + - The loc is captured at the point of error via `interpreter_error()` +- After `exec_item()`, consumer reads `snuk_interpreter_clear_error(intpret)` to get the error +- Consumer also calls `snuk_value_get_error(val)` for per-value errors + +## Consumer (REPL) Flow + +```c +SnukValue val = snuk_interpreter_exec_item(&rt->interpreter, item); +SnukError err = snuk_interpreter_clear_error(&rt->interpreter); + +if (err.code != SNUK_ERROR_NONE) { + // REPL formats however it wants: + // "Error[E12] at line 5, col 12: type mismatch" + snuk_eprintln("Error[E%d] at line %u, col %u: %s", + err.code, err.loc.line, err.loc.col, err.msg); +} +``` + +No formatting logic in the library — just data. + +## Implementation Plan + +### Phase 1: Core Types and Parser + +1. Create `include/snuk/snuk_error.h` — define `SnukSrcLoc`, `SnukErrorKind`, `SnukError`, parse/runtime error code enums +2. Update `SnukParser` struct — store `SnukError` instead of `err_msg`/`err_token` +3. Update `parser_error()` and `parser_sync()` — work with `SnukError` +4. Update `SNUK_ITEM_ERROR` to carry `SnukError` instead of raw string +5. Update `snuk_parser_next_item()` — clear error each iteration +6. Remove old `error_code.h` and related files (or keep for compatibility) + +### Phase 2: Interpreter Integration + +7. Add `SnukError` + `SnukSrcLoc cur_loc` to `SnukInterpreter` struct, replace `err_code` field +8. Update `interpreter_error(intpret, kind, code, msg)` — captures current `cur_loc` into the error +9. Add `interpreter_set_loc(intpret, line, col)` — set `cur_loc` before each expression evaluation +10. Pass loc info through `snuk_interpreter_exec_item(intpret, item)` — execution starts with `cur_loc` set from the item's token position (token positions from parser are passed in) +11. Each `execute_*` function updates `cur_loc` at its entry point for error precision +12. Update `execute_call_expr()` error guard to use new error type +13. Update `snuk_interpreter_exec_item()` to transfer errors to returned values + +### Phase 3: Consumer + +13. Update `snuk_runtime_execute()` — read structured errors, format for display +14. Remove all `log_error()` calls from interpreter (library produces data, not output) +15. Verify REPL and file mode work identically + +## Key Decisions + +- **Library vs consumer boundary**: The library NEVER prints errors. It returns structured data. The consumer decides format and output. +- **Location tracking**: Single `SnukSrcLoc` on the interpreter (updated before each expression) rather than adding loc to every AST node. Simpler to implement, adequate precision. +- **Error codes**: Separate enums for parse vs runtime, both with NONE = 0 for zero-init safety. +- **Error latching**: Same "first error wins" pattern as current implementation. +- **Backward compatibility**: The old `SnukErrorCode` enum and `error_code.h` are removed. The new `SnukError` type replaces both the old `err_code` field in `SnukInterpreter` and the old `err_msg`/`err_token` fields in `SnukParser`. diff --git a/include/snuk/interpreter/error_code.h b/include/snuk/interpreter/error_code.h deleted file mode 100644 index 6dc6f20..0000000 --- a/include/snuk/interpreter/error_code.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "snuk/defines.h" - -typedef enum SnukErrorCode { - SNUK_ERROR_CODE_NONE = 0, - SNUK_ERROR_SHOULD_NOT_REACH_HERE, - SNUK_ERROR_SOMETHING_WENT_WRONG, - SNUK_ERROR_CONTROL_FLOW, - SNUK_ERROR_EXISTS, - SNUK_ERROR_NON_TYPE, - SNUK_ERROR_EXPECT_ASSIGN, - SNUK_ERROR_BUILTIN_INVALID_VALUE, - SNUK_ERROR_MEMBER_INITIALIZE, - SNUK_ERROR_SELF_CREATION, - SNUK_ERROR_PARAM_CREATION, - SNUK_ERROR_NON_FN, - SNUK_ERROR_PARAM_COUNT, - SNUK_ERROR_NO_PARAM, - SNUK_ERROR_PARAM, - SNUK_ERROR_PARAM_REQUIRED, - SNUK_ERROR_SELF, - SNUK_ERROR_SET_ENV_FAIL, - SNUK_ERROR_MEMBER, - SNUK_ERROR_INTERFACE, - SNUK_ERROR_TYPE_MISMATCH, - - SNUK_ERROR_MAX, -} SnukErrorCode; - -SNUK_API const char *snuk_error_code_get_msg(SnukErrorCode code); diff --git a/include/snuk/interpreter/interpreter.h b/include/snuk/interpreter/interpreter.h index 9bcfe20..97575bb 100644 --- a/include/snuk/interpreter/interpreter.h +++ b/include/snuk/interpreter/interpreter.h @@ -5,6 +5,7 @@ #include "snuk/parser/snuk_expr.h" #include "snuk/parser/snuk_item.h" #include "snuk/refcount.h" +#include "snuk/snuk_error.h" #include "snuk/string_view.h" #include "snuk_env.h" #include "snuk_signal.h" @@ -29,7 +30,8 @@ typedef struct SnukInterpreter { void *mem; SnukAllocator allocator; snLinearAllocator la; - SnukErrorCode err_code; + SnukError err; + SnukSrcLoc cur_loc; } SnukInterpreter; /** @@ -90,3 +92,7 @@ SNUK_API bool snuk_interpreter_create_env( SnukInterpreter *intpret, SnukStringView name, SnukType *type, SnukValue value, bool is_const); SNUK_API bool snuk_interpreter_value_is_of_type(SnukInterpreter *intpret, SnukValue value, SnukType *type); + +SNUK_API SnukError snuk_interpreter_clear_error(SnukInterpreter *intpret); + +SNUK_API void snuk_interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col); diff --git a/include/snuk/interpreter/interpreter_helper.h b/include/snuk/interpreter/interpreter_helper.h index 439030d..ab4d8d3 100644 --- a/include/snuk/interpreter/interpreter_helper.h +++ b/include/snuk/interpreter/interpreter_helper.h @@ -1,7 +1,7 @@ #pragma once -#include "error_code.h" #include "interpreter.h" +#include "snuk/snuk_error.h" #include "snuk/darray.h" #include "snuk/defines.h" #include "snuk_scope.h" @@ -11,9 +11,19 @@ SnukValue execute_block_expr( SnukValue interpreter_copy_inst(SnukInterpreter *intpret, SnukValue inst); -SNUK_INLINE void interpreter_error(SnukInterpreter *intpret, SnukErrorCode err_code) { - if (intpret->err_code != SNUK_ERROR_CODE_NONE) return; - intpret->err_code = err_code; +SNUK_INLINE void interpreter_error(SnukInterpreter *intpret, SnukInterpError code, const char *msg) { + if (intpret->err.kind != SNUK_ERROR_KIND_NONE) return; + intpret->err = (SnukError){ + .kind = SNUK_ERROR_KIND_INTERP, + .code = (uint32_t)code, + .msg = msg, + .loc = intpret->cur_loc, + }; +} + +SNUK_INLINE void interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col) { + intpret->cur_loc.line = line; + intpret->cur_loc.col = col; } /** diff --git a/include/snuk/interpreter/snuk_value.h b/include/snuk/interpreter/snuk_value.h index e02475e..bf87136 100644 --- a/include/snuk/interpreter/snuk_value.h +++ b/include/snuk/interpreter/snuk_value.h @@ -1,6 +1,5 @@ #pragma once -#include "error_code.h" #include "snuk/defines.h" #include "snuk/parser/snuk_expr.h" #include "snuk/parser/snuk_type.h" @@ -43,7 +42,6 @@ typedef enum SnukValueType { */ struct SnukValue { SnukValueType type; - SnukErrorCode err_code; union { int64_t int_value; diff --git a/repl/runtime.c b/repl/runtime.c index cee15c8..445fbec 100644 --- a/repl/runtime.c +++ b/repl/runtime.c @@ -1,8 +1,8 @@ #include "runtime.h" -#include #include #include +#include void snuk_runtime_execute(Runtime *rt, const char *src) { SnukParser parser; @@ -15,8 +15,9 @@ void snuk_runtime_execute(Runtime *rt, const char *src) { // snuk_item_log(item); // log_trace("", NULL); SnukValue value = snuk_interpreter_exec_item(&rt->interpreter, item); - if (value.err_code) { - log_error("%s", snuk_error_code_get_msg(value.err_code)); + SnukError err = snuk_interpreter_clear_error(&rt->interpreter); + if (err.kind != SNUK_ERROR_KIND_NONE) { + log_error("%s", err.msg); } snuk_value_log(value); log_trace("", NULL); diff --git a/src/interpreter/CMakeLists.txt b/src/interpreter/CMakeLists.txt index ad5e9f9..1c4bf5c 100644 --- a/src/interpreter/CMakeLists.txt +++ b/src/interpreter/CMakeLists.txt @@ -5,7 +5,6 @@ set(PUBLIC_HEADERS snuk_scope.h snuk_env.h native.h - error_code.h ) set(HEADERS @@ -14,7 +13,6 @@ set(HEADERS set(SRCS interpreter.c snuk_value.c - error_code.c native.c ) diff --git a/src/interpreter/error_code.c b/src/interpreter/error_code.c deleted file mode 100644 index 043b40a..0000000 --- a/src/interpreter/error_code.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "snuk/interpreter/error_code.h" - -static const char *error_messages[] = { - [SNUK_ERROR_CODE_NONE] = "All is well", - [SNUK_ERROR_SHOULD_NOT_REACH_HERE] = "Shouldn't reach here", - [SNUK_ERROR_SOMETHING_WENT_WRONG] = "Something went wrong", - [SNUK_ERROR_CONTROL_FLOW] = "control flow item outside scope", - [SNUK_ERROR_EXISTS] = "variable already exists", - [SNUK_ERROR_NON_TYPE] = "expected a type", - [SNUK_ERROR_EXPECT_ASSIGN] = "expected assignment expression", - [SNUK_ERROR_BUILTIN_INVALID_VALUE] = "invalid value to builtin type member value", - [SNUK_ERROR_MEMBER_INITIALIZE] = "failed to initialize member", - [SNUK_ERROR_SELF_CREATION] = "failed to create self", - [SNUK_ERROR_PARAM_CREATION] = "failed to create parameter", - [SNUK_ERROR_NON_FN] = "call expression on non function", - [SNUK_ERROR_PARAM_COUNT] = "parameter count mismatch", - [SNUK_ERROR_NO_PARAM] = "parameter doesn't exists", - [SNUK_ERROR_PARAM] = "error in parameter passing", - [SNUK_ERROR_PARAM_REQUIRED] = "parameter is required", - [SNUK_ERROR_SELF] = "failed to get self", - [SNUK_ERROR_SET_ENV_FAIL] = "failed to set env value", - [SNUK_ERROR_MEMBER] = "couldn't find the member", - [SNUK_ERROR_INTERFACE] = "failed to create interface", - [SNUK_ERROR_TYPE_MISMATCH] = "types are not same", -}; - -const char *snuk_error_code_get_msg(SnukErrorCode code) { - return error_messages[code]; -} diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index 7022bbd..dfa535c 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -65,7 +65,8 @@ void snuk_interpreter_init(SnukInterpreter *intpret) { .realloc = realloc_fn, .free = free_fn, }, - .err_code = SNUK_ERROR_CODE_NONE, + .err = SNUK_ERROR_NONE, + .cur_loc = SNUK_SRC_LOC_NULL, }; sn_linear_allocator_init(&intpret->la, intpret->mem, PAGES * snuk_page_size()); intpret->current = snuk_ref_counter_retain(intpret->global); @@ -206,10 +207,7 @@ SnukValue snuk_interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item) { interpreter_clear_trash(intpret); SnukValue res = interpreter_exec_item(intpret, item, true); - if (intpret->signal != SNUK_SIGNAL_NONE) interpreter_error(intpret, SNUK_ERROR_CONTROL_FLOW); - - res.err_code = intpret->err_code; - intpret->err_code = SNUK_ERROR_CODE_NONE; + if (intpret->signal != SNUK_SIGNAL_NONE) interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); return res; } @@ -218,6 +216,16 @@ SnukValue snuk_interpreter_eval_expr(SnukInterpreter *intpret, SnukExpr *expr) { return interpreter_eval_expr(intpret, expr, true); } +SnukError snuk_interpreter_clear_error(SnukInterpreter *intpret) { + SnukError err = intpret->err; + intpret->err = SNUK_ERROR_NONE; + return err; +} + +void snuk_interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col) { + interpreter_set_loc(intpret, line, col); +} + /** * @brief Evaluate a unary expression's operand and apply the operator. */ @@ -402,7 +410,7 @@ static SnukValue case SNUK_VALUE_MAX: default: - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); break; } if (op == SNUK_TOKEN_BANG_EQUAL) res.bool_value = !res.bool_value; @@ -426,7 +434,7 @@ static SnukValue } fail: - interpreter_error(intpret, SNUK_ERROR_TYPE_MISMATCH); + interpreter_error(intpret, SNUK_INTERP_ERR_TYPE_MISMATCH, "types are not the same"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -552,7 +560,7 @@ static void interpreter_print_value(SnukInterpreter *intpret, SnukValue value) { break; default: - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); break; } } @@ -597,7 +605,7 @@ SnukValue execute_block_expr( } else if (intpret->signal & propogate_signals) { break; } else { - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); } } @@ -660,7 +668,7 @@ static SnukValue execute_while_expr(SnukInterpreter *intpret, SnukExpr *expr, bo goto end; case SNUK_SIGNAL_CONTINUE: - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); break; case SNUK_SIGNAL_NONE: @@ -693,7 +701,7 @@ static SnukValue execute_for_expr(SnukInterpreter *intpret, SnukExpr *expr, bool if (expr->for_loop.init) { SnukValue val = interpreter_exec_item(intpret, expr->for_loop.init, false); if (intpret->signal != SNUK_SIGNAL_NONE) - interpreter_error(intpret, SNUK_ERROR_CONTROL_FLOW); + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); snuk_value_free(val); } @@ -718,7 +726,7 @@ static SnukValue execute_for_expr(SnukInterpreter *intpret, SnukExpr *expr, bool goto end; case SNUK_SIGNAL_CONTINUE: - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); break; case SNUK_SIGNAL_NONE: @@ -766,7 +774,7 @@ static SnukValue execute_type_declaration(SnukInterpreter *intpret, SnukExpr *ex SnukValue val = interpreter_exec_item(intpret, expr->type_expr.members[i], true); snuk_value_free(val); if (intpret->signal != SNUK_SIGNAL_NONE) - interpreter_error(intpret, SNUK_ERROR_CONTROL_FLOW); + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); } interpreter_pop_scope(intpret); @@ -776,7 +784,7 @@ static SnukValue execute_type_declaration(SnukInterpreter *intpret, SnukExpr *ex // Syntax sugar if (expr->type_expr.name.len && !snuk_interpreter_create_env(intpret, expr->type_expr.name, value.type_value.type, value, false)) - interpreter_error(intpret, SNUK_ERROR_EXISTS); + interpreter_error(intpret, SNUK_INTERP_ERR_EXISTS, "variable already exists"); return value; } @@ -785,7 +793,7 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, SNUK_UNUSED(weak_ref); SnukValue type = snuk_interpreter_get_env(intpret, expr->type_inst_expr.type->name); if (type.type != SNUK_VALUE_TYPE) { - interpreter_error(intpret, SNUK_ERROR_NON_TYPE); + interpreter_error(intpret, SNUK_INTERP_ERR_NON_TYPE, "expected a type"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -810,7 +818,7 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, for (uint64_t i = 0; i < init_count; ++i) { SnukExpr *assign = expr->type_inst_expr.init[i]; if (assign->type != SNUK_EXPR_ASSIGN) { - interpreter_error(intpret, SNUK_ERROR_EXPECT_ASSIGN); + interpreter_error(intpret, SNUK_INTERP_ERR_EXPECT_ASSIGN, "expected assignment expression"); break; } @@ -821,10 +829,10 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, // if builtin type, make sure value of value member is right SnukValueType val_type = snuk_builtins_get_value_type(value.type_value.type->name); if (val_type != SNUK_VALUE_UNKOWN && snuk_string_view_equal(name, value_str)) - if (val.type != val_type) interpreter_error(intpret, SNUK_ERROR_BUILTIN_INVALID_VALUE); + if (val.type != val_type) interpreter_error(intpret, SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE, "invalid value for builtin type member"); if (!interpreter_set_member(intpret, value, name, val)) - interpreter_error(intpret, SNUK_ERROR_MEMBER_INITIALIZE); + interpreter_error(intpret, SNUK_INTERP_ERR_MEMBER_INITIALIZE, "failed to initialize member"); snuk_value_free(val); } @@ -833,7 +841,7 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, self_value.type_value.weak_ref = true; if (!snuk_interpreter_create_env(intpret, self_str, self_value.type_value.type, self_value, true)) - interpreter_error(intpret, SNUK_ERROR_SELF_CREATION); + interpreter_error(intpret, SNUK_INTERP_ERR_SELF_CREATION, "failed to create self"); snuk_value_free(self_value); @@ -847,7 +855,7 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, // Syntax sugar if (expr->type_inst_expr.name.len && !snuk_interpreter_create_env(intpret, expr->type_inst_expr.name, value.type_value.type, value, false)) - interpreter_error(intpret, SNUK_ERROR_EXISTS); + interpreter_error(intpret, SNUK_INTERP_ERR_EXISTS, "variable already exists"); interpreter_trash(intpret, type); @@ -921,7 +929,7 @@ static SnukValue execute_fn_expr(SnukInterpreter *intpret, SnukExpr *expr, bool SnukValue value = (SnukValue){.type = SNUK_VALUE_UNKOWN}; if (param->value) value = interpreter_eval_expr(intpret, param->value, false); if (!snuk_interpreter_create_env(intpret, param->name, param->type, value, false)) - interpreter_error(intpret, SNUK_ERROR_PARAM_CREATION); + interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_CREATION, "failed to create parameter"); snuk_value_free(value); } @@ -943,7 +951,7 @@ static SnukValue execute_fn_expr(SnukInterpreter *intpret, SnukExpr *expr, bool // Syntax sugar if (expr->fn_expr.name.len && !snuk_interpreter_create_env(intpret, expr->fn_expr.name, value.fn_value.type, value, false)) - interpreter_error(intpret, SNUK_ERROR_EXISTS); + interpreter_error(intpret, SNUK_INTERP_ERR_EXISTS, "variable already exists"); return value; } @@ -955,7 +963,7 @@ static SnukValue execute_fn_expr(SnukInterpreter *intpret, SnukExpr *expr, bool static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, bool weak_ref) { SnukValue fn = interpreter_eval_expr(intpret, expr->call.fn, weak_ref); if (fn.type != SNUK_VALUE_FN && fn.type != SNUK_VALUE_FN_NATIVE) { - interpreter_error(intpret, SNUK_ERROR_NON_FN); + interpreter_error(intpret, SNUK_INTERP_ERR_NON_FN, "call expression on non-function"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -970,7 +978,7 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo uint64_t fn_param_count = snuk_darray_get_length(fn_scope->vars); uint64_t param_count = snuk_darray_get_length(expr->call.params); - if (fn_param_count < param_count) interpreter_error(intpret, SNUK_ERROR_PARAM_COUNT); + if (fn_param_count < param_count) interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_COUNT, "parameter count mismatch"); bool named_params = false; for (uint64_t i = 0; i < param_count; ++i) { @@ -987,7 +995,7 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo name = param->assign.identifier->identifier; fn_env = snuk_scope_lookup(fn_scope_rc, name, NULL); if (!fn_env) { - interpreter_error(intpret, SNUK_ERROR_NO_PARAM); + interpreter_error(intpret, SNUK_INTERP_ERR_NO_PARAM, "parameter doesn't exist"); break; } type = fn_env->type; @@ -997,13 +1005,13 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo type = fn_env->type; value = param; } else { - interpreter_error(intpret, SNUK_ERROR_PARAM); + interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_MIXED, "mixed positional and named parameters"); break; } SnukValue val = interpreter_eval_expr(intpret, value, true); if (!snuk_interpreter_create_env(intpret, name, type, val, false)) - interpreter_error(intpret, SNUK_ERROR_PARAM_CREATION); + interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_CREATION, "failed to create parameter"); snuk_value_free(val); } @@ -1014,10 +1022,10 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo SnukEnv *env = snuk_scope_lookup(intpret->current, fn_env->name, NULL); if (!env) { if (fn_env->value.type == SNUK_VALUE_UNKOWN) - interpreter_error(intpret, SNUK_ERROR_PARAM_REQUIRED); + interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_REQUIRED, "required parameter missing"); if (!snuk_interpreter_create_env(intpret, fn_env->name, fn_env->type, fn_env->value, false)) - interpreter_error(intpret, SNUK_ERROR_SOMETHING_WENT_WRONG); + interpreter_error(intpret, SNUK_INTERP_ERR_SOMETHING_WENT_WRONG, "something went wrong"); } } @@ -1026,7 +1034,7 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo interpreter_pop_scope(intpret); - if (intpret->err_code) { + if (intpret->err.kind != SNUK_ERROR_KIND_NONE) { snuk_ref_counter_release(&new_scope); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -1119,7 +1127,7 @@ static SnukValue interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item, SnukValue value = interpreter_eval_expr(intpret, item->var->value, weak_ref); if (!snuk_interpreter_create_env( intpret, item->var->name, item->var->type, value, item->type == SNUK_ITEM_CONST_DECL)) - interpreter_error(intpret, SNUK_ERROR_EXISTS); + interpreter_error(intpret, SNUK_INTERP_ERR_EXISTS, "variable already exists"); return value; } @@ -1156,7 +1164,7 @@ static SnukValue interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item, break; } - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -1242,7 +1250,7 @@ static SnukValue interpreter_eval_expr(SnukInterpreter *intpret, SnukExpr *expr, case SNUK_EXPR_SELF: { SnukValue self_value = snuk_interpreter_get_env(intpret, self_str); if (self_value.type != SNUK_VALUE_TYPE_INST) - interpreter_error(intpret, SNUK_ERROR_SELF); + interpreter_error(intpret, SNUK_INTERP_ERR_SELF, "failed to get self"); return self_value; } @@ -1257,7 +1265,7 @@ static SnukValue interpreter_eval_expr(SnukInterpreter *intpret, SnukExpr *expr, break; } - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -1268,20 +1276,20 @@ static SnukValue execute_assign_expr(SnukInterpreter *intpret, SnukExpr *expr, b case SNUK_EXPR_IDENTIFIER: if (!snuk_interpreter_set_env(intpret, identifier->identifier, value)) - interpreter_error(intpret, SNUK_ERROR_SET_ENV_FAIL); + interpreter_error(intpret, SNUK_INTERP_ERR_SET_ENV_FAIL, "failed to set env value"); break; case SNUK_EXPR_MEMBER: { SnukExpr *field = identifier->member_access.field; SnukValue type_or_inst = interpreter_eval_expr(intpret, identifier->member_access.type, weak_ref); if (!interpreter_set_member(intpret, type_or_inst, field->identifier, value)) - interpreter_error(intpret, SNUK_ERROR_SET_ENV_FAIL); + interpreter_error(intpret, SNUK_INTERP_ERR_SET_ENV_FAIL, "failed to set env value"); interpreter_trash(intpret, type_or_inst); break; } default: - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); break; } return value; @@ -1336,7 +1344,7 @@ static SnukValue execute_member_get(SnukInterpreter *intpret, SnukExpr *expr, bo }; break; default: - interpreter_error(intpret, SNUK_ERROR_SHOULD_NOT_REACH_HERE); + interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); break; } @@ -1366,7 +1374,7 @@ static SnukValue execute_member_get(SnukInterpreter *intpret, SnukExpr *expr, bo res.native_fn.instance = snuk_ref_counter_retain_weak(type_or_inst.type_value.closure); } - if (res.type == SNUK_VALUE_UNKOWN) interpreter_error(intpret, SNUK_ERROR_MEMBER); + if (res.type == SNUK_VALUE_UNKOWN) interpreter_error(intpret, SNUK_INTERP_ERR_MEMBER, "couldn't find the member"); interpreter_trash(intpret, type_or_inst); return res; @@ -1375,7 +1383,7 @@ static SnukValue execute_member_get(SnukInterpreter *intpret, SnukExpr *expr, bo static SnukValue execute_extend(SnukInterpreter *intpret, SnukItem *item, bool weak_ref) { SnukValue type = interpreter_eval_expr(intpret, item->extend_item.type, weak_ref); if (type.type != SNUK_VALUE_TYPE) { - interpreter_error(intpret, SNUK_ERROR_NON_TYPE); + interpreter_error(intpret, SNUK_INTERP_ERR_NON_TYPE, "expected a type"); return type; } @@ -1387,7 +1395,7 @@ static SnukValue execute_extend(SnukInterpreter *intpret, SnukItem *item, bool w SnukValue val = interpreter_exec_item(intpret, item->extend_item.members[i], true); snuk_value_free(val); if (intpret->signal != SNUK_SIGNAL_NONE) - interpreter_error(intpret, SNUK_ERROR_CONTROL_FLOW); + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); } type.type_value.closure = snuk_ref_counter_move(&intpret->current); @@ -1405,7 +1413,7 @@ static SnukValue execute_interface(SnukInterpreter *intpret, SnukItem *item, boo }, }; if (!snuk_interpreter_create_env(intpret, item->interface_item.name, item->interface_item.type, value, false)) - interpreter_error(intpret, SNUK_ERROR_INTERFACE); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERFACE, "failed to create interface"); return value; } @@ -1433,7 +1441,7 @@ SnukValue interpreter_copy_inst(SnukInterpreter *intpret, SnukValue inst) { else val = snuk_value_copy(scope->vars[i]->value); if (!snuk_interpreter_create_env( intpret, scope->vars[i]->name, scope->vars[i]->type, val, scope->vars[i]->is_const)) - interpreter_error(intpret, SNUK_ERROR_MEMBER_INITIALIZE); + interpreter_error(intpret, SNUK_INTERP_ERR_MEMBER_INITIALIZE, "failed to initialize member"); snuk_value_free(val); } @@ -1442,7 +1450,7 @@ SnukValue interpreter_copy_inst(SnukInterpreter *intpret, SnukValue inst) { self_value.type_value.weak_ref = true; if (!snuk_interpreter_create_env(intpret, self_str, self_value.type_value.type, self_value, true)) - interpreter_error(intpret, SNUK_ERROR_SELF_CREATION); + interpreter_error(intpret, SNUK_INTERP_ERR_SELF_CREATION, "failed to create self"); snuk_value_free(self_value); From ad08ef06fe5f06b992b9119152947f0ed824ce68 Mon Sep 17 00:00:00 2001 From: kshku <1211shree@gmail.com> Date: Mon, 8 Jun 2026 13:04:02 +0530 Subject: [PATCH 5/8] feat: update REPL display and remove library log_error --- repl/runtime.c | 14 +++++++++++--- src/interpreter/interpreter.c | 5 ++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/repl/runtime.c b/repl/runtime.c index 445fbec..b24aa26 100644 --- a/repl/runtime.c +++ b/repl/runtime.c @@ -12,12 +12,20 @@ void snuk_runtime_execute(Runtime *rt, const char *src) { while (true) { item = snuk_parser_next_item(&parser); if (!item) break; - // snuk_item_log(item); - // log_trace("", NULL); + + SnukError parse_err = snuk_parser_clear_error(&parser); + if (parse_err.kind != SNUK_ERROR_KIND_NONE) { + log_error("[Error] at line %u, col %u: %s", parse_err.loc.line, parse_err.loc.col, parse_err.msg); + continue; + } + SnukValue value = snuk_interpreter_exec_item(&rt->interpreter, item); SnukError err = snuk_interpreter_clear_error(&rt->interpreter); if (err.kind != SNUK_ERROR_KIND_NONE) { - log_error("%s", err.msg); + if (err.loc.line) + log_error("[Error] at line %u, col %u: %s", err.loc.line, err.loc.col, err.msg); + else + log_error("[Error] %s", err.msg); } snuk_value_log(value); log_trace("", NULL); diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index dfa535c..25ea0f7 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -1156,7 +1156,10 @@ static SnukValue interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item, return execute_interface(intpret, item, weak_ref); case SNUK_ITEM_ERROR: - log_error("Error: %s", item->error.error.msg); + if (intpret->err.kind == SNUK_ERROR_KIND_NONE) { + intpret->err = item->error.error; + } + return (SnukValue){.type = SNUK_VALUE_NULL}; return (SnukValue){.type = SNUK_VALUE_NULL}; case SNUK_ITEM_MAX: From 44e9b182d36bd7ffa37a194f238513914a484e1c Mon Sep 17 00:00:00 2001 From: kshku <1211shree@gmail.com> Date: Mon, 8 Jun 2026 13:04:34 +0530 Subject: [PATCH 6/8] chore: final cleanup and verification --- include/snuk/interpreter/interpreter.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/snuk/interpreter/interpreter.h b/include/snuk/interpreter/interpreter.h index 97575bb..b9b6d55 100644 --- a/include/snuk/interpreter/interpreter.h +++ b/include/snuk/interpreter/interpreter.h @@ -20,6 +20,9 @@ * and for loops push and pop scopes. global is retained for the lifetime of * the interpreter so identifiers can fall through to the root. signal carries * the most recent control-flow signal raised during evaluation. + * + * err holds the first error encountered during item execution. The consumer + * reads it via snuk_interpreter_clear_error() after each exec_item call. */ typedef struct SnukInterpreter { SnukRefCounter *current; From c56f6a7722832063f6d2053ef0b41e8897ddd988 Mon Sep 17 00:00:00 2001 From: kshku <1211shree@gmail.com> Date: Tue, 9 Jun 2026 07:02:10 +0530 Subject: [PATCH 7/8] refactor: redesign interpreter error system into categories with detailed messages SnukInterpError collapsed from 20 to 7 categories: - TYPE: type system violations - NAME: name resolution/declaration conflicts - FUNCALL: function call errors - ASSIGN: assignment/initialization failures - CONTROL_FLOW: break/continue/return outside valid scope - INTERFACE: interface creation errors - INTERNAL: unreachable code paths Added err_msg_buf[256] + interpreter_error_fmt() for formatted messages with variable names. All 39 error call sites updated with detailed, context-specific messages. Removed committed docs/superpowers design files. --- .../plans/2025-01-01-error-reporting-plan.md | 660 ------------------ .../2025-01-01-error-reporting-design.md | 129 ---- include/snuk/interpreter/interpreter.h | 1 + include/snuk/interpreter/interpreter_helper.h | 17 + include/snuk/snuk_error.h | 54 +- src/interpreter/interpreter.c | 90 +-- 6 files changed, 83 insertions(+), 868 deletions(-) delete mode 100644 docs/superpowers/plans/2025-01-01-error-reporting-plan.md delete mode 100644 docs/superpowers/specs/2025-01-01-error-reporting-design.md diff --git a/docs/superpowers/plans/2025-01-01-error-reporting-plan.md b/docs/superpowers/plans/2025-01-01-error-reporting-plan.md deleted file mode 100644 index 1175aee..0000000 --- a/docs/superpowers/plans/2025-01-01-error-reporting-plan.md +++ /dev/null @@ -1,660 +0,0 @@ -# Error Reporting Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Replace the current ad-hoc error reporting with a clean library-produces-data, consumer-formats architecture. - -**Architecture:** A single `SnukError` POD type shared by parser and interpreter. Both produce structured errors with source location (line/col). The REPL/consumer formats and prints. The library never prints. - -**Tech Stack:** C17, CMake, existing test infrastructure - ---- - -## File Structure - -- **Create:** `include/snuk/snuk_error.h` — `SnukSrcLoc`, `SnukErrorKind`, `SnukError`, runtime/parse error code enums -- **Modify:** `include/snuk/interpreter/interpreter.h` — replace `SnukErrorCode err_code` with `SnukError err` + `SnukSrcLoc cur_loc` -- **Modify:** `include/snuk/interpreter/interpreter_helper.h` — update `interpreter_error()`, add `interpreter_set_loc()` -- **Modify:** `src/interpreter/interpreter.c` — update all error calls, add loc tracking at expression entry points -- **Modify:** `include/snuk/parser/parser.h` — replace `err_msg`/`err_token` with `SnukError err` -- **Modify:** `include/snuk/parser/snuk_item.h` — replace `const char *msg` with `SnukError error` in error item -- **Modify:** `src/parser/parser.c` — update `parser_error()`, `parser_sync()` -- **Modify:** `src/parser/parser_common.h` — update `parser_advance()`, `parser_expect()` -- **Modify:** `repl/runtime.c` — format `SnukError` for display -- **Remove:** `include/snuk/interpreter/error_code.h` — replaced by `snuk_error.h` -- **Remove:** `src/interpreter/error_code.c` — replaced by `snuk_error.h` -- **Remove:** `repl/runtime.h` — inline functions (no changes needed but verify) - ---- - -### Task 1: Create `snuk_error.h` with core types - -**Files:** -- Create: `include/snuk/snuk_error.h` -- Modify: None - -- [ ] **Step 1: Write the header file** - -```c -#pragma once - -#include - -typedef struct { - const char *file; // filename or NULL - uint32_t line; // 1-indexed - uint32_t col; // 1-indexed -} SnukSrcLoc; - -#define SNUK_SRC_LOC_NULL ((SnukSrcLoc){NULL, 0, 0}) - -typedef enum { - SNUK_ERROR_KIND_NONE = 0, - SNUK_ERROR_KIND_PARSE, - SNUK_ERROR_KIND_INTERP, -} SnukErrorKind; - -typedef enum { - SNUK_INTERP_ERR_NONE = 0, - SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, - SNUK_INTERP_ERR_SOMETHING_WENT_WRONG, - SNUK_INTERP_ERR_CONTROL_FLOW, - SNUK_INTERP_ERR_EXISTS, - SNUK_INTERP_ERR_NON_TYPE, - SNUK_INTERP_ERR_EXPECT_ASSIGN, - SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE, - SNUK_INTERP_ERR_MEMBER_INITIALIZE, - SNUK_INTERP_ERR_SELF_CREATION, - SNUK_INTERP_ERR_PARAM_CREATION, - SNUK_INTERP_ERR_NON_FN, - SNUK_INTERP_ERR_PARAM_COUNT, - SNUK_INTERP_ERR_NO_PARAM, - SNUK_INTERP_ERR_PARAM_MIXED, - SNUK_INTERP_ERR_PARAM_REQUIRED, - SNUK_INTERP_ERR_SELF, - SNUK_INTERP_ERR_SET_ENV_FAIL, - SNUK_INTERP_ERR_MEMBER, - SNUK_INTERP_ERR_INTERFACE, - SNUK_INTERP_ERR_TYPE_MISMATCH, -} SnukInterpError; - -typedef enum { - SNUK_PARSE_ERR_NONE = 0, - SNUK_PARSE_ERR_UNEXPECTED_TOKEN, - SNUK_PARSE_ERR_EXPECTED_IDENTIFIER, - SNUK_PARSE_ERR_EXPECTED_TYPE_ANNOTATION, - SNUK_PARSE_ERR_EXPECTED_SEMICOLON_OR_NEWLINE, - SNUK_PARSE_ERR_EXPECTED_CLOSE_BRACE, - SNUK_PARSE_ERR_EXPECTED_CLOSE_PAREN, - SNUK_PARSE_ERR_EXPECTED_CLOSE_BRACKET, - SNUK_PARSE_ERR_EXPECTED_KEYWORD, - SNUK_PARSE_ERR_EXPECTED_EXPRESSION, - SNUK_PARSE_ERR_EXPECTED_TYPE, - SNUK_PARSE_ERR_EXPECTED_MEMBER, - SNUK_PARSE_ERR_INVALID_LITERAL, - SNUK_PARSE_ERR_UNTERMINATED_STRING, - SNUK_PARSE_ERR_LEXER_ERROR, - SNUK_PARSE_ERR_UNKNOWN, -} SnukParseError; - -typedef struct { - SnukErrorKind kind; - uint32_t code; // cast from SnukInterpError or SnukParseError - const char *msg; // human-readable description - SnukSrcLoc loc; -} SnukError; - -#define SNUK_ERROR_NONE ((SnukError){SNUK_ERROR_KIND_NONE, 0, NULL, SNUK_SRC_LOC_NULL}) - -static inline const char *snuk_interp_error_msg(SnukInterpError code) { - switch (code) { - case SNUK_INTERP_ERR_NONE: return "no error"; - case SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE: return "shouldn't reach here"; - case SNUK_INTERP_ERR_SOMETHING_WENT_WRONG: return "something went wrong"; - case SNUK_INTERP_ERR_CONTROL_FLOW: return "control flow item outside scope"; - case SNUK_INTERP_ERR_EXISTS: return "variable already exists"; - case SNUK_INTERP_ERR_NON_TYPE: return "expected a type"; - case SNUK_INTERP_ERR_EXPECT_ASSIGN: return "expected assignment expression"; - case SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE: return "invalid value for builtin type member"; - case SNUK_INTERP_ERR_MEMBER_INITIALIZE: return "failed to initialize member"; - case SNUK_INTERP_ERR_SELF_CREATION: return "failed to create self"; - case SNUK_INTERP_ERR_PARAM_CREATION: return "failed to create parameter"; - case SNUK_INTERP_ERR_NON_FN: return "call expression on non-function"; - case SNUK_INTERP_ERR_PARAM_COUNT: return "parameter count mismatch"; - case SNUK_INTERP_ERR_NO_PARAM: return "parameter doesn't exist"; - case SNUK_INTERP_ERR_PARAM_MIXED: return "mixed positional and named parameters"; - case SNUK_INTERP_ERR_PARAM_REQUIRED: return "required parameter missing"; - case SNUK_INTERP_ERR_SELF: return "failed to get self"; - case SNUK_INTERP_ERR_SET_ENV_FAIL: return "failed to set env value"; - case SNUK_INTERP_ERR_MEMBER: return "couldn't find the member"; - case SNUK_INTERP_ERR_INTERFACE: return "failed to create interface"; - case SNUK_INTERP_ERR_TYPE_MISMATCH: return "types are not the same"; - } - return "unknown error"; -} -``` - -- [ ] **Step 2: Verify it compiles in isolation** - -Run: `echo '#include "snuk_error.h"' | gcc -std=c17 -fsyntax-only -I include -x c -` -Expected: no errors - -- [ ] **Step 3: Commit** - -```bash -git add include/snuk/snuk_error.h -git commit -m "feat: add SnukError types for structured error reporting" -``` - ---- - -### Task 2: Update parser to use SnukError - -**Files:** -- Modify: `include/snuk/parser/parser.h` -- Modify: `include/snuk/parser/snuk_item.h` -- Modify: `src/parser/parser.c` -- Modify: `src/parser/parser_common.h` - -- [ ] **Step 1: Update `include/snuk/parser/parser.h`** - -Replace `err_msg`/`err_token` with a single `SnukError err`: - -```c -#include "snuk/snuk_error.h" - -typedef struct SnukParser { - SnukLexer *lexer; - SnukToken previous; - SnukToken current; - SnukToken next; - bool panic_mode; - SnukError err; // <-- replace err_msg + err_token - struct { - char *buffer; - uint64_t length; - } string_buffer; -} SnukParser; -``` - -Also add `snuk_parser_clear_error()` declaration: - -```c -SnukError snuk_parser_clear_error(SnukParser *parser); -``` - -- [ ] **Step 2: Update `include/snuk/parser/snuk_item.h`** - -Replace the error item's `const char *msg` with `SnukError error`: - -```c -#include "snuk/snuk_error.h" - -// Inside the item union: -struct { - SnukError error; // <-- was const char *msg + SnukToken token -} error; -``` - -Remove `build_error_item()` and replace with a version that takes `SnukError`: - -```c -SnukItem *snuk_build_error_item(SnukAllocator allocator, SnukError error); -``` - -- [ ] **Step 3: Update `src/parser/parser.c`** - -Replace `parser_error()`: - -```c -#include "snuk/lexer.h" - -void parser_error(SnukParser *parser, SnukParseError code, const char *msg) { - if (parser->panic_mode) return; - parser->panic_mode = true; - parser->err = (SnukError){ - .kind = SNUK_ERROR_KIND_PARSE, - .code = code, - .msg = msg, - .loc = { - .line = parser->current.line, - .col = parser->current.col, - }, - }; -} -``` - -Update `parser_sync()`: - -```c -static SnukItem *parser_sync(SnukParser *parser) { - while (parser->current.type != SNUK_TOKEN_EOF - && parser->current.type != SNUK_TOKEN_SEMICOLON - && parser->current.type != SNUK_TOKEN_NEWLINE) { - parser_advance(parser); - } - SnukItem *item = snuk_build_error_item(parser->allocator, parser->err); - parser->panic_mode = false; - return item; -} -``` - -- [ ] **Step 4: Update `src/parser/parser_common.h`** - -Update `parser_advance()`: - -```c -#define parser_advance(parser) \ - do { \ - (parser)->previous = (parser)->current; \ - (parser)->current = (parser)->next; \ - (parser)->next = snuk_lexer_next_token((parser)->lexer); \ - if ((parser)->current.type == SNUK_TOKEN_ERROR) \ - parser_error(parser, SNUK_PARSE_ERR_LEXER_ERROR, "lexer error"); \ - } while (0) -``` - -Update `parser_expect()`: - -```c -static inline bool parser_expect(SnukParser *parser, SnukTokenType type, const char *msg) { - if (parser->current.type == type) { - parser_advance(parser); - return true; - } - parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, msg); - return false; -} -``` - -- [ ] **Step 5: Update `snuk_build_error_item()` in snuk_item.h** - -```c -SnukItem *snuk_build_error_item(SnukAllocator allocator, SnukError error) { - SnukItem *item = snuk_alloc_item(allocator); - if (!item) return NULL; - item->type = SNUK_ITEM_ERROR; - item->error.error = error; - return item; -} -``` - -- [ ] **Step 6: Update all parser .c files that call `parser_error()`** - -These files currently call `parser_error(parser, "some string")`. Update them all: - -```c -// Old: -parser_error(parser, "expected identifier"); - -// New: -parser_error(parser, SNUK_PARSE_ERR_EXPECTED_IDENTIFIER, "expected identifier"); -``` - -Files to update (grep for `parser_error`): -- `src/parser/snuk_expr.c` -- `src/parser/snuk_item.c` -- `src/parser/snuk_type.c` - -- [ ] **Step 7: Build and verify parser compiles** - -Run: `cmake --build build --target snuk_repl -j$(nproc) 2>&1` -Expected: compiles with no errors - -- [ ] **Step 8: Run existing .snuk tests to verify parser still works** - -Run: `ctest --test-dir build/tests -L snuk_files --output-on-failure` -Expected: all pass - -- [ ] **Step 9: Commit** - -```bash -git add include/snuk/parser/parser.h include/snuk/parser/snuk_item.h src/parser/ -git commit -m "feat: update parser to use SnukError" -``` - ---- - -### Task 3: Update interpreter to use SnukError - -**Files:** -- Modify: `include/snuk/interpreter/interpreter.h` -- Modify: `include/snuk/interpreter/interpreter_helper.h` -- Modify: `include/snuk/interpreter/snuk_value.h` -- Modify: `src/interpreter/interpreter.c` -- Remove: `include/snuk/interpreter/error_code.h` -- Remove: `src/interpreter/error_code.c` - -- [ ] **Step 1: Update `include/snuk/interpreter/interpreter.h`** - -Replace `SnukErrorCode err_code` with `SnukError err` + `SnukSrcLoc cur_loc`: - -```c -#include "snuk/snuk_error.h" - -typedef struct SnukInterpreter { - SnukRefCounter *global; - SnukRefCounter *current; - SnukRefCounter *instance; - - SnukError err; // <-- replaces SnukErrorCode err_code - SnukSrcLoc cur_loc; // current source location for error reporting - - SnukDarray /* SnukValue * */ trash; - SnukSignal signal; - // ... -} SnukInterpreter; -``` - -Remove `#include "error_code.h"` from the includes. - -Add declarations: -```c -SnukError snuk_interpreter_clear_error(SnukInterpreter *intpret); -void snuk_interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col); -``` - -- [ ] **Step 2: Update `include/snuk/interpreter/snuk_value.h`** - -Replace `SnukErrorCode err_code` with a cleaner approach — keep for now but rename to avoid confusion. Actually, the simplest approach: keep `SnukErrorCode err_code` field for now but typedef `SnukErrorCode` as a generic uint32_t. Or better, just remove it from the value and have the error be on the interpreter only. - -Actually, the current system copies `err_code` from the interpreter onto the returned value to communicate errors. With the new system, the consumer calls `snuk_interpreter_clear_error()` after `exec_item()`. So we don't need `err_code` on the value anymore. - -Remove `SnukErrorCode err_code;` from the `SnukValue` struct. Adjust all references. - -- [ ] **Step 3: Update `include/snuk/interpreter/interpreter_helper.h`** - -Update `interpreter_error()`: - -```c -#include "snuk/snuk_error.h" - -static inline void interpreter_error(SnukInterpreter *intpret, SnukInterpError code, const char *msg) { - if (intpret->err.kind != SNUK_ERROR_KIND_NONE) return; // first error wins - intpret->err = (SnukError){ - .kind = SNUK_ERROR_KIND_INTERP, - .code = code, - .msg = msg, - .loc = intpret->cur_loc, - }; -} -``` - -Add `interpreter_set_loc()`: - -```c -static inline void interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col) { - intpret->cur_loc.line = line; - intpret->cur_loc.col = col; -} -``` - -- [ ] **Step 4: Update `src/interpreter/interpreter.c`** - -Update all `interpreter_error(intpret, SNUK_ERROR_XXX)` calls to `interpreter_error(intpret, SNUK_INTERP_ERR_XXX, msg)`. - -Update `snuk_interpreter_exec_item()`: - -```c -SnukValue snuk_interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item) { - interpreter_clear_trash(intpret); - - // Set cur_loc from item if available (parser items track line/col) - // For now, we just clear the error at the start of each item - SnukValue res = interpreter_exec_item(intpret, item, true); - - if (intpret->signal != SNUK_SIGNAL_NONE) { - interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); - } - - // Error is now on intpret->err, not copied to value - // Consumer reads via snuk_interpreter_clear_error() - return res; -} -``` - -Add `snuk_interpreter_clear_error()`: - -```c -SnukError snuk_interpreter_clear_error(SnukInterpreter *intpret) { - SnukError err = intpret->err; - intpret->err = (SnukError)SNUK_ERROR_NONE; - return err; -} - -void snuk_interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col) { - interpreter_set_loc(intpret, line, col); -} -``` - -Update all `interpreter_error(ptr, SNUK_ERROR_*)` calls throughout the file: - -```c -// Old: -interpreter_error(intpret, SNUK_ERROR_NON_FN); - -// New: -interpreter_error(intpret, SNUK_INTERP_ERR_NON_FN, "call expression on non-function"); -``` - -Grep for all `SNUK_ERROR_` references in the file and replace: -- `SNUK_ERROR_NONE` → check `intpret->err.kind == SNUK_ERROR_KIND_NONE` -- `SNUK_ERROR_CONTROL_FLOW` → `SNUK_INTERP_ERR_CONTROL_FLOW` -- `SNUK_ERROR_EXISTS` → `SNUK_INTERP_ERR_EXISTS` -- etc. - -The only error guard in `execute_call_expr()` (around line 1029): - -```c -if (intpret->err.kind != SNUK_ERROR_KIND_NONE) { - snuk_ref_counter_release(&new_scope); - return (SnukValue){.type = SNUK_VALUE_UNKOWN}; -} -``` - -- [ ] **Step 5: Remove old error files** - -Delete `include/snuk/interpreter/error_code.h` and `src/interpreter/error_code.c`. -Remove the `error_code.c` source from `CMakeLists.txt` in the interpreter directory. - -- [ ] **Step 6: Build and verify** - -Run: `cmake --build build --target snuk_repl -j$(nproc) 2>&1` -Expected: compiles with no errors - -- [ ] **Step 7: Run tests** - -Run: `ctest --test-dir build/tests -L snuk_files --output-on-failure` -Expected: all pass (some tests may need regex update — see Task 5) - -- [ ] **Step 8: Commit** - -```bash -git add include/snuk/interpreter/ src/interpreter/ -git rm include/snuk/interpreter/error_code.h src/interpreter/error_code.c -git commit -m "feat: update interpreter to use SnukError" -``` - ---- - -### Task 4: Thread source location through interpreter - -**Files:** -- Modify: `src/interpreter/interpreter.c` - -- [ ] **Step 1: Add `interpreter_set_loc()` calls at expression entry points** - -In each `execute_*` function, set the loc at entry. Since we don't have loc on every AST node yet, use a simple approach: the parser sets line/col from the item's token when calling `snuk_interpreter_exec_item()`, and each expression evaluator updates it. - -For now, update `snuk_interpreter_exec_item()` to accept an optional start location and set it on the interpreter: - -```c -SnukValue snuk_interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item, SnukSrcLoc start_loc) { - interpreter_clear_trash(intpret); - intpret->cur_loc = start_loc; - // ... rest of function -} -``` - -But wait — changing the signature breaks the public API. Better to have a separate setter called by the runtime before exec_item: - -```c -// In runtime.c: -snuk_interpreter_set_loc(&rt->interpreter, line, col); -SnukValue val = snuk_interpreter_exec_item(&rt->interpreter, item); -``` - -The parser can't easily provide line/col per item right now (the token info is in the parser, not on the item). For the initial implementation, just set loc to (0,0) or the last-known token position from the parser. - -- [ ] **Step 2: Build and test** - -Run: `cmake --build build --target snuk_repl -j$(nproc) && ctest --test-dir build/tests -L snuk_files --output-on-failure` -Expected: all pass - -- [ ] **Step 3: Commit** - -```bash -git add src/interpreter/interpreter.c include/snuk/interpreter/interpreter.h -git commit -m "feat: add cur_loc tracking to interpreter" -``` - ---- - -### Task 5: Update runtime/REPL to display errors - -**Files:** -- Modify: `repl/runtime.c` -- Modify: `CMakeLists.txt` (if needed for test regex) - -- [ ] **Step 1: Update `repl/runtime.c`** - -Replace the error display: - -```c -#include - -void snuk_runtime_execute(Runtime *rt, const char *src) { - SnukParser parser; - snuk_parser_init(&parser, src, &rt->parser_allocator); - - SnukItem *item; - while (true) { - item = snuk_parser_next_item(&parser); - if (!item) break; - - // Handle parser errors - SnukError parse_err = snuk_parser_clear_error(&parser); - if (parse_err.kind != SNUK_ERROR_KIND_NONE) { - snuk_println("[Error] at line %u, col %u: %s", - parse_err.loc.line, parse_err.loc.col, parse_err.msg); - continue; - } - - SnukValue value = snuk_interpreter_exec_item(&rt->interpreter, item); - - // Handle interpreter errors - SnukError interp_err = snuk_interpreter_clear_error(&rt->interpreter); - if (interp_err.kind != SNUK_ERROR_KIND_NONE) { - snuk_println("[Error] at line %u, col %u: %s", - interp_err.loc.line, interp_err.loc.col, interp_err.msg); - } - - snuk_value_log(value); - log_trace("", NULL); - snuk_value_free(value); - } - - snuk_parser_deinit(&parser); -} -``` - -- [ ] **Step 2: Update test regex in `tests/CMakeLists.txt`** - -The test regex checks for "ERROR|error". Our new format says "[Error]" which also matches. But let's verify: - -Run: `ctest --test-dir build/tests -L snuk_files --output-on-failure` -Expected: all pass - -If tests fail because error output format changed, update `FAIL_REGULAR_EXPRESSION` to match the new format. - -- [ ] **Step 3: Commit** - -```bash -git add repl/runtime.c tests/CMakeLists.txt -git commit -m "feat: update REPL to display structured SnukError" -``` - ---- - -### Task 6: Remove library-side log_error calls - -**Files:** -- Modify: `src/interpreter/interpreter.c` -- Modify: `src/parser/` (if any log_error calls exist) - -- [ ] **Step 1: Remove `log_error` calls from interpreter** - -The interpreter currently has one `log_error` call in the `SNUK_ITEM_ERROR` handler (line 1150-1152): - -```c -case SNUK_ITEM_ERROR: - log_error("Error: %s", item->error.msg); - return (SnukValue){.type = SNUK_VALUE_NULL}; -``` - -Replace with: - -```c -case SNUK_ITEM_ERROR: - // Error data is in item->error.error; consumer handles display - // Store parser error on interpreter for the consumer to read - if (intpret->err.kind == SNUK_ERROR_KIND_NONE) { - intpret->err = item->error.error; // propagate parser error through interpreter - } - return (SnukValue){.type = SNUK_VALUE_NULL}; -``` - -- [ ] **Step 2: Build and test** - -Run: `cmake --build build --target snuk_repl -j$(nproc) && ctest --test-dir build/tests -L snuk_files --output-on-failure` -Expected: all pass - -- [ ] **Step 3: Commit** - -```bash -git add src/interpreter/interpreter.c -git commit -m "feat: remove library-side log_error, propagate SnukError through interpreter" -``` - ---- - -### Task 7: Clean up includes and verify everything compiles - -**Files:** -- All modified files - -- [ ] **Step 1: Remove obsolete includes** - -Search for leftover `#include "error_code.h"` references and replace with `#include "snuk/snuk_error.h"`: - -```bash -grep -rn "error_code.h" include/ src/ repl/ --include="*.h" --include="*.c" -``` - -Also update any `error_code.h` includes that need to point to `snuk_error.h`. - -- [ ] **Step 2: Full rebuild and test** - -```bash -cmake --build build -j$(nproc) 2>&1 -ctest --test-dir build/tests --output-on-failure -``` - -Expected: all compile, all tests pass - -- [ ] **Step 3: Commit** - -```bash -git add -A -git commit -m "chore: clean up includes, remove old error_code references" -``` diff --git a/docs/superpowers/specs/2025-01-01-error-reporting-design.md b/docs/superpowers/specs/2025-01-01-error-reporting-design.md deleted file mode 100644 index c720dde..0000000 --- a/docs/superpowers/specs/2025-01-01-error-reporting-design.md +++ /dev/null @@ -1,129 +0,0 @@ -# Error Reporting Design - -## Goals - -- Library produces structured error data; the consumer (REPL/embedder) handles formatting and display -- Both parser and interpreter produce errors through the same type -- Errors carry source location (line, col) for user-friendly messages -- No formatting, printing, or I/O in the library — pure data - -## Data Types - -```c -typedef struct { - const char *file; // filename or NULL (unknown source) - uint32_t line; // 1-indexed - uint32_t col; // 1-indexed -} SnukSrcLoc; - -typedef enum { - SNUK_ERROR_KIND_PARSE, - SNUK_ERROR_KIND_RUNTIME, -} SnukErrorKind; - -typedef struct { - SnukErrorKind kind; - uint32_t code; // error code (e.g., TYPE_MISMATCH, PARAM_COUNT) - const char *msg; // human-readable description, no newlines - SnukSrcLoc loc; -} SnukError; -``` - -`SnukError` is POD — no allocation, returned by value, copied freely. - -## Error Codes - -Replace the current `SnukErrorCode` enum with separate enums for parse vs runtime errors: - -```c -// Parse errors -typedef enum { - SNUK_PARSE_ERR_NONE = 0, - SNUK_PARSE_ERR_UNEXPECTED_TOKEN, - SNUK_PARSE_ERR_EXPECTED_IDENTIFIER, - SNUK_PARSE_ERR_EXPECTED_SEMICOLON, - SNUK_PARSE_ERR_EXPECTED_CLOSE_BRACE, - // ... -} SnukParseErrorCode; - -// Runtime errors -typedef enum { - SNUK_RUNTIME_ERR_NONE = 0, - SNUK_RUNTIME_ERR_TYPE_MISMATCH, - SNUK_RUNTIME_ERR_NON_FUNCTION, - SNUK_RUNTIME_ERR_PARAM_COUNT, - SNUK_RUNTIME_ERR_UNDEFINED_VARIABLE, - SNUK_RUNTIME_ERR_DUPLICATE_VARIABLE, - // ... -} SnukRuntimeErrorCode; -``` - -## Parser Flow - -- `parser_error(parser, kind, msg)` → builds `SnukError` from the current token's line/col, stores it in the parser -- Parser continues producing `SNUK_ITEM_ERROR` items, but the error item carries a full `SnukError` instead of a raw string -- Consumer reads `snuk_parser_clear_error(parser)` after each `next_item()` call - -## Interpreter Flow - -- The interpreter struct gets a `SnukError err` field (one-shot latch, same pattern as current `err_code`) -- `interpreter_error(intpret, kind, msg, loc)` → sets `intpret->err` if no error is already latched -- Location tracking: - - The interpreter has a single `SnukSrcLoc cur_loc` field, updated before each expression is evaluated - - Each `execute_*` function calls `interpreter_set_loc(intpret, line, col)` before evaluating - - The parser already computes line/col for each token; pass this through to the interpreter - - NO changes to AST nodes needed — loc is tracked in the interpreter, not on expressions - - The loc is captured at the point of error via `interpreter_error()` -- After `exec_item()`, consumer reads `snuk_interpreter_clear_error(intpret)` to get the error -- Consumer also calls `snuk_value_get_error(val)` for per-value errors - -## Consumer (REPL) Flow - -```c -SnukValue val = snuk_interpreter_exec_item(&rt->interpreter, item); -SnukError err = snuk_interpreter_clear_error(&rt->interpreter); - -if (err.code != SNUK_ERROR_NONE) { - // REPL formats however it wants: - // "Error[E12] at line 5, col 12: type mismatch" - snuk_eprintln("Error[E%d] at line %u, col %u: %s", - err.code, err.loc.line, err.loc.col, err.msg); -} -``` - -No formatting logic in the library — just data. - -## Implementation Plan - -### Phase 1: Core Types and Parser - -1. Create `include/snuk/snuk_error.h` — define `SnukSrcLoc`, `SnukErrorKind`, `SnukError`, parse/runtime error code enums -2. Update `SnukParser` struct — store `SnukError` instead of `err_msg`/`err_token` -3. Update `parser_error()` and `parser_sync()` — work with `SnukError` -4. Update `SNUK_ITEM_ERROR` to carry `SnukError` instead of raw string -5. Update `snuk_parser_next_item()` — clear error each iteration -6. Remove old `error_code.h` and related files (or keep for compatibility) - -### Phase 2: Interpreter Integration - -7. Add `SnukError` + `SnukSrcLoc cur_loc` to `SnukInterpreter` struct, replace `err_code` field -8. Update `interpreter_error(intpret, kind, code, msg)` — captures current `cur_loc` into the error -9. Add `interpreter_set_loc(intpret, line, col)` — set `cur_loc` before each expression evaluation -10. Pass loc info through `snuk_interpreter_exec_item(intpret, item)` — execution starts with `cur_loc` set from the item's token position (token positions from parser are passed in) -11. Each `execute_*` function updates `cur_loc` at its entry point for error precision -12. Update `execute_call_expr()` error guard to use new error type -13. Update `snuk_interpreter_exec_item()` to transfer errors to returned values - -### Phase 3: Consumer - -13. Update `snuk_runtime_execute()` — read structured errors, format for display -14. Remove all `log_error()` calls from interpreter (library produces data, not output) -15. Verify REPL and file mode work identically - -## Key Decisions - -- **Library vs consumer boundary**: The library NEVER prints errors. It returns structured data. The consumer decides format and output. -- **Location tracking**: Single `SnukSrcLoc` on the interpreter (updated before each expression) rather than adding loc to every AST node. Simpler to implement, adequate precision. -- **Error codes**: Separate enums for parse vs runtime, both with NONE = 0 for zero-init safety. -- **Error latching**: Same "first error wins" pattern as current implementation. -- **Backward compatibility**: The old `SnukErrorCode` enum and `error_code.h` are removed. The new `SnukError` type replaces both the old `err_code` field in `SnukInterpreter` and the old `err_msg`/`err_token` fields in `SnukParser`. diff --git a/include/snuk/interpreter/interpreter.h b/include/snuk/interpreter/interpreter.h index b9b6d55..1d4a2ea 100644 --- a/include/snuk/interpreter/interpreter.h +++ b/include/snuk/interpreter/interpreter.h @@ -35,6 +35,7 @@ typedef struct SnukInterpreter { snLinearAllocator la; SnukError err; SnukSrcLoc cur_loc; + char err_msg_buf[256]; } SnukInterpreter; /** diff --git a/include/snuk/interpreter/interpreter_helper.h b/include/snuk/interpreter/interpreter_helper.h index ab4d8d3..a00b7a5 100644 --- a/include/snuk/interpreter/interpreter_helper.h +++ b/include/snuk/interpreter/interpreter_helper.h @@ -6,6 +6,9 @@ #include "snuk/defines.h" #include "snuk_scope.h" +#include +#include + SnukValue execute_block_expr( SnukInterpreter *intpret, SnukExpr *block, int capture_signals, int propogate_signals, bool weak_ref); @@ -21,6 +24,20 @@ SNUK_INLINE void interpreter_error(SnukInterpreter *intpret, SnukInterpError cod }; } +SNUK_INLINE void interpreter_error_fmt(SnukInterpreter *intpret, SnukInterpError code, const char *fmt, ...) { + if (intpret->err.kind != SNUK_ERROR_KIND_NONE) return; + va_list args; + va_start(args, fmt); + vsnprintf(intpret->err_msg_buf, sizeof(intpret->err_msg_buf), fmt, args); + va_end(args); + intpret->err = (SnukError){ + .kind = SNUK_ERROR_KIND_INTERP, + .code = (uint32_t)code, + .msg = intpret->err_msg_buf, + .loc = intpret->cur_loc, + }; +} + SNUK_INLINE void interpreter_set_loc(SnukInterpreter *intpret, uint32_t line, uint32_t col) { intpret->cur_loc.line = line; intpret->cur_loc.col = col; diff --git a/include/snuk/snuk_error.h b/include/snuk/snuk_error.h index c2be95e..0ca3986 100644 --- a/include/snuk/snuk_error.h +++ b/include/snuk/snuk_error.h @@ -18,26 +18,13 @@ typedef enum { typedef enum { SNUK_INTERP_ERR_NONE = 0, - SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, - SNUK_INTERP_ERR_SOMETHING_WENT_WRONG, - SNUK_INTERP_ERR_CONTROL_FLOW, - SNUK_INTERP_ERR_EXISTS, - SNUK_INTERP_ERR_NON_TYPE, - SNUK_INTERP_ERR_EXPECT_ASSIGN, - SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE, - SNUK_INTERP_ERR_MEMBER_INITIALIZE, - SNUK_INTERP_ERR_SELF_CREATION, - SNUK_INTERP_ERR_PARAM_CREATION, - SNUK_INTERP_ERR_NON_FN, - SNUK_INTERP_ERR_PARAM_COUNT, - SNUK_INTERP_ERR_NO_PARAM, - SNUK_INTERP_ERR_PARAM_MIXED, - SNUK_INTERP_ERR_PARAM_REQUIRED, - SNUK_INTERP_ERR_SELF, - SNUK_INTERP_ERR_SET_ENV_FAIL, - SNUK_INTERP_ERR_MEMBER, - SNUK_INTERP_ERR_INTERFACE, - SNUK_INTERP_ERR_TYPE_MISMATCH, + SNUK_INTERP_ERR_TYPE, // type system violations + SNUK_INTERP_ERR_NAME, // name resolution / declaration conflicts + SNUK_INTERP_ERR_FUNCALL, // function call errors + SNUK_INTERP_ERR_ASSIGN, // assignment / initialization failures + SNUK_INTERP_ERR_CONTROL_FLOW, // break/continue/return outside valid scope + SNUK_INTERP_ERR_INTERFACE, // interface creation errors + SNUK_INTERP_ERR_INTERNAL, // unexpected/unreachable code paths } SnukInterpError; typedef enum { @@ -71,26 +58,13 @@ typedef struct { static inline const char *snuk_interp_error_msg(SnukInterpError code) { switch (code) { case SNUK_INTERP_ERR_NONE: return "no error"; - case SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE: return "shouldn't reach here"; - case SNUK_INTERP_ERR_SOMETHING_WENT_WRONG: return "something went wrong"; - case SNUK_INTERP_ERR_CONTROL_FLOW: return "control flow item outside scope"; - case SNUK_INTERP_ERR_EXISTS: return "variable already exists"; - case SNUK_INTERP_ERR_NON_TYPE: return "expected a type"; - case SNUK_INTERP_ERR_EXPECT_ASSIGN: return "expected assignment expression"; - case SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE: return "invalid value for builtin type member"; - case SNUK_INTERP_ERR_MEMBER_INITIALIZE: return "failed to initialize member"; - case SNUK_INTERP_ERR_SELF_CREATION: return "failed to create self"; - case SNUK_INTERP_ERR_PARAM_CREATION: return "failed to create parameter"; - case SNUK_INTERP_ERR_NON_FN: return "call expression on non-function"; - case SNUK_INTERP_ERR_PARAM_COUNT: return "parameter count mismatch"; - case SNUK_INTERP_ERR_NO_PARAM: return "parameter doesn't exist"; - case SNUK_INTERP_ERR_PARAM_MIXED: return "mixed positional and named parameters"; - case SNUK_INTERP_ERR_PARAM_REQUIRED: return "required parameter missing"; - case SNUK_INTERP_ERR_SELF: return "failed to get self"; - case SNUK_INTERP_ERR_SET_ENV_FAIL: return "failed to set env value"; - case SNUK_INTERP_ERR_MEMBER: return "couldn't find the member"; - case SNUK_INTERP_ERR_INTERFACE: return "failed to create interface"; - case SNUK_INTERP_ERR_TYPE_MISMATCH: return "types are not the same"; + case SNUK_INTERP_ERR_TYPE: return "type error"; + case SNUK_INTERP_ERR_NAME: return "name error"; + case SNUK_INTERP_ERR_FUNCALL: return "function call error"; + case SNUK_INTERP_ERR_ASSIGN: return "assignment error"; + case SNUK_INTERP_ERR_CONTROL_FLOW: return "control flow error"; + case SNUK_INTERP_ERR_INTERFACE: return "interface error"; + case SNUK_INTERP_ERR_INTERNAL: return "internal error"; } return "unknown error"; } diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index 25ea0f7..20eb1fe 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -207,7 +207,7 @@ SnukValue snuk_interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item) { interpreter_clear_trash(intpret); SnukValue res = interpreter_exec_item(intpret, item, true); - if (intpret->signal != SNUK_SIGNAL_NONE) interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); + if (intpret->signal != SNUK_SIGNAL_NONE) interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "break/continue/return outside of function or loop scope"); return res; } @@ -410,7 +410,7 @@ static SnukValue case SNUK_VALUE_MAX: default: - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "unexpected value type in comparison"); break; } if (op == SNUK_TOKEN_BANG_EQUAL) res.bool_value = !res.bool_value; @@ -434,7 +434,7 @@ static SnukValue } fail: - interpreter_error(intpret, SNUK_INTERP_ERR_TYPE_MISMATCH, "types are not the same"); + interpreter_error(intpret, SNUK_INTERP_ERR_TYPE, "type mismatch: incompatible operand types in binary operation"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -560,7 +560,7 @@ static void interpreter_print_value(SnukInterpreter *intpret, SnukValue value) { break; default: - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "unhandled value type in value printer"); break; } } @@ -605,7 +605,7 @@ SnukValue execute_block_expr( } else if (intpret->signal & propogate_signals) { break; } else { - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "unexpected control flow signal in block execution"); } } @@ -668,7 +668,7 @@ static SnukValue execute_while_expr(SnukInterpreter *intpret, SnukExpr *expr, bo goto end; case SNUK_SIGNAL_CONTINUE: - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "continue signal not captured in while-loop signal mask"); break; case SNUK_SIGNAL_NONE: @@ -701,7 +701,7 @@ static SnukValue execute_for_expr(SnukInterpreter *intpret, SnukExpr *expr, bool if (expr->for_loop.init) { SnukValue val = interpreter_exec_item(intpret, expr->for_loop.init, false); if (intpret->signal != SNUK_SIGNAL_NONE) - interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "break/continue/return not allowed in for-loop initializer"); snuk_value_free(val); } @@ -726,7 +726,7 @@ static SnukValue execute_for_expr(SnukInterpreter *intpret, SnukExpr *expr, bool goto end; case SNUK_SIGNAL_CONTINUE: - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "continue signal not captured in for-loop signal mask"); break; case SNUK_SIGNAL_NONE: @@ -774,7 +774,7 @@ static SnukValue execute_type_declaration(SnukInterpreter *intpret, SnukExpr *ex SnukValue val = interpreter_exec_item(intpret, expr->type_expr.members[i], true); snuk_value_free(val); if (intpret->signal != SNUK_SIGNAL_NONE) - interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "break/continue/return not allowed in type declaration body"); } interpreter_pop_scope(intpret); @@ -784,7 +784,8 @@ static SnukValue execute_type_declaration(SnukInterpreter *intpret, SnukExpr *ex // Syntax sugar if (expr->type_expr.name.len && !snuk_interpreter_create_env(intpret, expr->type_expr.name, value.type_value.type, value, false)) - interpreter_error(intpret, SNUK_INTERP_ERR_EXISTS, "variable already exists"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_NAME, "variable '%.*s' already exists in this scope", + (int)expr->type_expr.name.len, expr->type_expr.name.str); return value; } @@ -793,7 +794,7 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, SNUK_UNUSED(weak_ref); SnukValue type = snuk_interpreter_get_env(intpret, expr->type_inst_expr.type->name); if (type.type != SNUK_VALUE_TYPE) { - interpreter_error(intpret, SNUK_INTERP_ERR_NON_TYPE, "expected a type"); + interpreter_error(intpret, SNUK_INTERP_ERR_TYPE, "expected a type value for instance creation"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -818,7 +819,7 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, for (uint64_t i = 0; i < init_count; ++i) { SnukExpr *assign = expr->type_inst_expr.init[i]; if (assign->type != SNUK_EXPR_ASSIGN) { - interpreter_error(intpret, SNUK_INTERP_ERR_EXPECT_ASSIGN, "expected assignment expression"); + interpreter_error(intpret, SNUK_INTERP_ERR_ASSIGN, "expected assignment expression in instance initializer"); break; } @@ -829,10 +830,10 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, // if builtin type, make sure value of value member is right SnukValueType val_type = snuk_builtins_get_value_type(value.type_value.type->name); if (val_type != SNUK_VALUE_UNKOWN && snuk_string_view_equal(name, value_str)) - if (val.type != val_type) interpreter_error(intpret, SNUK_INTERP_ERR_BUILTIN_INVALID_VALUE, "invalid value for builtin type member"); + if (val.type != val_type) interpreter_error_fmt(intpret, SNUK_INTERP_ERR_TYPE, "invalid value type for builtin member 'value': expected a different type"); if (!interpreter_set_member(intpret, value, name, val)) - interpreter_error(intpret, SNUK_INTERP_ERR_MEMBER_INITIALIZE, "failed to initialize member"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_ASSIGN, "failed to initialize member '%.*s' in instance", (int)name.len, name.str); snuk_value_free(val); } @@ -841,7 +842,7 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, self_value.type_value.weak_ref = true; if (!snuk_interpreter_create_env(intpret, self_str, self_value.type_value.type, self_value, true)) - interpreter_error(intpret, SNUK_INTERP_ERR_SELF_CREATION, "failed to create self"); + interpreter_error(intpret, SNUK_INTERP_ERR_ASSIGN, "failed to create 'self' reference in instance scope"); snuk_value_free(self_value); @@ -855,7 +856,8 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, // Syntax sugar if (expr->type_inst_expr.name.len && !snuk_interpreter_create_env(intpret, expr->type_inst_expr.name, value.type_value.type, value, false)) - interpreter_error(intpret, SNUK_INTERP_ERR_EXISTS, "variable already exists"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_NAME, "variable '%.*s' already exists in this scope", + (int)expr->type_inst_expr.name.len, expr->type_inst_expr.name.str); interpreter_trash(intpret, type); @@ -929,7 +931,8 @@ static SnukValue execute_fn_expr(SnukInterpreter *intpret, SnukExpr *expr, bool SnukValue value = (SnukValue){.type = SNUK_VALUE_UNKOWN}; if (param->value) value = interpreter_eval_expr(intpret, param->value, false); if (!snuk_interpreter_create_env(intpret, param->name, param->type, value, false)) - interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_CREATION, "failed to create parameter"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_NAME, "failed to create function parameter '%.*s'", + (int)param->name.len, param->name.str); snuk_value_free(value); } @@ -951,7 +954,8 @@ static SnukValue execute_fn_expr(SnukInterpreter *intpret, SnukExpr *expr, bool // Syntax sugar if (expr->fn_expr.name.len && !snuk_interpreter_create_env(intpret, expr->fn_expr.name, value.fn_value.type, value, false)) - interpreter_error(intpret, SNUK_INTERP_ERR_EXISTS, "variable already exists"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_NAME, "function '%.*s' already exists in this scope", + (int)expr->fn_expr.name.len, expr->fn_expr.name.str); return value; } @@ -963,7 +967,7 @@ static SnukValue execute_fn_expr(SnukInterpreter *intpret, SnukExpr *expr, bool static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, bool weak_ref) { SnukValue fn = interpreter_eval_expr(intpret, expr->call.fn, weak_ref); if (fn.type != SNUK_VALUE_FN && fn.type != SNUK_VALUE_FN_NATIVE) { - interpreter_error(intpret, SNUK_INTERP_ERR_NON_FN, "call expression on non-function"); + interpreter_error(intpret, SNUK_INTERP_ERR_FUNCALL, "call expression on non-function value"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -978,7 +982,7 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo uint64_t fn_param_count = snuk_darray_get_length(fn_scope->vars); uint64_t param_count = snuk_darray_get_length(expr->call.params); - if (fn_param_count < param_count) interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_COUNT, "parameter count mismatch"); + if (fn_param_count < param_count) interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "function expects %llu argument(s) but %llu provided", (unsigned long long)fn_param_count, (unsigned long long)param_count); bool named_params = false; for (uint64_t i = 0; i < param_count; ++i) { @@ -995,7 +999,7 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo name = param->assign.identifier->identifier; fn_env = snuk_scope_lookup(fn_scope_rc, name, NULL); if (!fn_env) { - interpreter_error(intpret, SNUK_INTERP_ERR_NO_PARAM, "parameter doesn't exist"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "unknown parameter '%.*s'", (int)name.len, name.str); break; } type = fn_env->type; @@ -1005,13 +1009,14 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo type = fn_env->type; value = param; } else { - interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_MIXED, "mixed positional and named parameters"); + interpreter_error(intpret, SNUK_INTERP_ERR_FUNCALL, "cannot mix positional and named arguments"); break; } SnukValue val = interpreter_eval_expr(intpret, value, true); if (!snuk_interpreter_create_env(intpret, name, type, val, false)) - interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_CREATION, "failed to create parameter"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "failed to bind argument to parameter '%.*s'", + (int)name.len, name.str); snuk_value_free(val); } @@ -1022,10 +1027,11 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo SnukEnv *env = snuk_scope_lookup(intpret->current, fn_env->name, NULL); if (!env) { if (fn_env->value.type == SNUK_VALUE_UNKOWN) - interpreter_error(intpret, SNUK_INTERP_ERR_PARAM_REQUIRED, "required parameter missing"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "required parameter '%.*s' is missing", + (int)fn_env->name.len, fn_env->name.str); if (!snuk_interpreter_create_env(intpret, fn_env->name, fn_env->type, fn_env->value, false)) - interpreter_error(intpret, SNUK_INTERP_ERR_SOMETHING_WENT_WRONG, "something went wrong"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "failed to apply default value for parameter"); } } @@ -1127,7 +1133,8 @@ static SnukValue interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item, SnukValue value = interpreter_eval_expr(intpret, item->var->value, weak_ref); if (!snuk_interpreter_create_env( intpret, item->var->name, item->var->type, value, item->type == SNUK_ITEM_CONST_DECL)) - interpreter_error(intpret, SNUK_INTERP_ERR_EXISTS, "variable already exists"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_NAME, "variable '%.*s' already exists in this scope", + (int)item->var->name.len, item->var->name.str); return value; } @@ -1167,7 +1174,7 @@ static SnukValue interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item, break; } - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "unhandled item type in item executor"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -1253,7 +1260,7 @@ static SnukValue interpreter_eval_expr(SnukInterpreter *intpret, SnukExpr *expr, case SNUK_EXPR_SELF: { SnukValue self_value = snuk_interpreter_get_env(intpret, self_str); if (self_value.type != SNUK_VALUE_TYPE_INST) - interpreter_error(intpret, SNUK_INTERP_ERR_SELF, "failed to get self"); + interpreter_error(intpret, SNUK_INTERP_ERR_NAME, "'self' is not available in this context"); return self_value; } @@ -1268,7 +1275,7 @@ static SnukValue interpreter_eval_expr(SnukInterpreter *intpret, SnukExpr *expr, break; } - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "unhandled expression type in expression evaluator"); return (SnukValue){.type = SNUK_VALUE_UNKOWN}; } @@ -1279,20 +1286,22 @@ static SnukValue execute_assign_expr(SnukInterpreter *intpret, SnukExpr *expr, b case SNUK_EXPR_IDENTIFIER: if (!snuk_interpreter_set_env(intpret, identifier->identifier, value)) - interpreter_error(intpret, SNUK_INTERP_ERR_SET_ENV_FAIL, "failed to set env value"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_ASSIGN, "cannot assign to '%.*s': variable not found or type mismatch", + (int)identifier->identifier.len, identifier->identifier.str); break; case SNUK_EXPR_MEMBER: { SnukExpr *field = identifier->member_access.field; SnukValue type_or_inst = interpreter_eval_expr(intpret, identifier->member_access.type, weak_ref); if (!interpreter_set_member(intpret, type_or_inst, field->identifier, value)) - interpreter_error(intpret, SNUK_INTERP_ERR_SET_ENV_FAIL, "failed to set env value"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_ASSIGN, "cannot assign to member '%.*s': not found or locked", + (int)field->identifier.len, field->identifier.str); interpreter_trash(intpret, type_or_inst); break; } default: - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "unhandled identifier type in assignment"); break; } return value; @@ -1347,7 +1356,7 @@ static SnukValue execute_member_get(SnukInterpreter *intpret, SnukExpr *expr, bo }; break; default: - interpreter_error(intpret, SNUK_INTERP_ERR_SHOULD_NOT_REACH_HERE, "shouldn't reach here"); + interpreter_error(intpret, SNUK_INTERP_ERR_INTERNAL, "unhandled primitive type in member access wrapper"); break; } @@ -1377,7 +1386,8 @@ static SnukValue execute_member_get(SnukInterpreter *intpret, SnukExpr *expr, bo res.native_fn.instance = snuk_ref_counter_retain_weak(type_or_inst.type_value.closure); } - if (res.type == SNUK_VALUE_UNKOWN) interpreter_error(intpret, SNUK_INTERP_ERR_MEMBER, "couldn't find the member"); + if (res.type == SNUK_VALUE_UNKOWN) interpreter_error_fmt(intpret, SNUK_INTERP_ERR_NAME, "type or instance has no member '%.*s'", + (int)expr->member_access.field->identifier.len, expr->member_access.field->identifier.str); interpreter_trash(intpret, type_or_inst); return res; @@ -1386,7 +1396,7 @@ static SnukValue execute_member_get(SnukInterpreter *intpret, SnukExpr *expr, bo static SnukValue execute_extend(SnukInterpreter *intpret, SnukItem *item, bool weak_ref) { SnukValue type = interpreter_eval_expr(intpret, item->extend_item.type, weak_ref); if (type.type != SNUK_VALUE_TYPE) { - interpreter_error(intpret, SNUK_INTERP_ERR_NON_TYPE, "expected a type"); + interpreter_error(intpret, SNUK_INTERP_ERR_TYPE, "expected a type value for extend"); return type; } @@ -1398,7 +1408,7 @@ static SnukValue execute_extend(SnukInterpreter *intpret, SnukItem *item, bool w SnukValue val = interpreter_exec_item(intpret, item->extend_item.members[i], true); snuk_value_free(val); if (intpret->signal != SNUK_SIGNAL_NONE) - interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "control flow item outside scope"); + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "break/continue/return not allowed in extend body"); } type.type_value.closure = snuk_ref_counter_move(&intpret->current); @@ -1416,7 +1426,8 @@ static SnukValue execute_interface(SnukInterpreter *intpret, SnukItem *item, boo }, }; if (!snuk_interpreter_create_env(intpret, item->interface_item.name, item->interface_item.type, value, false)) - interpreter_error(intpret, SNUK_INTERP_ERR_INTERFACE, "failed to create interface"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_INTERFACE, "failed to create interface '%.*s'", + (int)item->interface_item.name.len, item->interface_item.name.str); return value; } @@ -1444,7 +1455,8 @@ SnukValue interpreter_copy_inst(SnukInterpreter *intpret, SnukValue inst) { else val = snuk_value_copy(scope->vars[i]->value); if (!snuk_interpreter_create_env( intpret, scope->vars[i]->name, scope->vars[i]->type, val, scope->vars[i]->is_const)) - interpreter_error(intpret, SNUK_INTERP_ERR_MEMBER_INITIALIZE, "failed to initialize member"); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_ASSIGN, "failed to initialize member '%.*s' in instance copy", + (int)scope->vars[i]->name.len, scope->vars[i]->name.str); snuk_value_free(val); } @@ -1453,7 +1465,7 @@ SnukValue interpreter_copy_inst(SnukInterpreter *intpret, SnukValue inst) { self_value.type_value.weak_ref = true; if (!snuk_interpreter_create_env(intpret, self_str, self_value.type_value.type, self_value, true)) - interpreter_error(intpret, SNUK_INTERP_ERR_SELF_CREATION, "failed to create self"); + interpreter_error(intpret, SNUK_INTERP_ERR_ASSIGN, "failed to create 'self' reference in instance copy"); snuk_value_free(self_value); From 50abf7d235943a724d16173dd912bef9a9a57578 Mon Sep 17 00:00:00 2001 From: kshku <1211shree@gmail.com> Date: Tue, 9 Jun 2026 07:09:08 +0530 Subject: [PATCH 8/8] chore: apply clang-format --- include/snuk/interpreter/interpreter_helper.h | 4 +- include/snuk/parser/parser_common.h | 6 ++- include/snuk/parser/snuk_item.h | 2 +- include/snuk/snuk_error.h | 46 +++++++++++-------- repl/runtime.c | 6 +-- src/interpreter/interpreter.c | 34 +++++++++----- src/parser/parser.c | 6 +-- src/parser/snuk_type.c | 3 +- 8 files changed, 65 insertions(+), 42 deletions(-) diff --git a/include/snuk/interpreter/interpreter_helper.h b/include/snuk/interpreter/interpreter_helper.h index a00b7a5..d2a7c62 100644 --- a/include/snuk/interpreter/interpreter_helper.h +++ b/include/snuk/interpreter/interpreter_helper.h @@ -1,13 +1,13 @@ #pragma once #include "interpreter.h" -#include "snuk/snuk_error.h" #include "snuk/darray.h" #include "snuk/defines.h" +#include "snuk/snuk_error.h" #include "snuk_scope.h" -#include #include +#include SnukValue execute_block_expr( SnukInterpreter *intpret, SnukExpr *block, int capture_signals, int propogate_signals, bool weak_ref); diff --git a/include/snuk/parser/parser_common.h b/include/snuk/parser/parser_common.h index 5fa2adb..60bd636 100644 --- a/include/snuk/parser/parser_common.h +++ b/include/snuk/parser/parser_common.h @@ -17,7 +17,8 @@ typedef struct SnukVar SnukVar; SNUK_INLINE void parser_advance(SnukParser *parser) { parser->previous = parser->current; parser->current = parser->next; - if (parser->current.type == SNUK_TOKEN_ERROR) parser_error(parser, SNUK_PARSE_ERR_LEXER_ERROR, "lexer error"); + if (parser->current.type == SNUK_TOKEN_ERROR) + parser_error(parser, SNUK_PARSE_ERR_LEXER_ERROR, "lexer error"); parser->next = snuk_lexer_next_token(&parser->lexer); } @@ -101,7 +102,8 @@ SNUK_INLINE bool parser_match_item_end(SnukParser *parser) { * @param parser Parser context to operate on. */ SNUK_INLINE void parser_expect_item_end(SnukParser *parser) { - if (!parser_match_item_end(parser)) parser_error(parser, SNUK_PARSE_ERR_EXPECTED_SEMICOLON_OR_NEWLINE, "expected a new line or a semicolon"); + if (!parser_match_item_end(parser)) + parser_error(parser, SNUK_PARSE_ERR_EXPECTED_SEMICOLON_OR_NEWLINE, "expected a new line or a semicolon"); } SNUK_INLINE SnukStringView parser_copy_string_view(SnukParser *parser, SnukStringView sv) { diff --git a/include/snuk/parser/snuk_item.h b/include/snuk/parser/snuk_item.h index ddcf1ef..14d1900 100644 --- a/include/snuk/parser/snuk_item.h +++ b/include/snuk/parser/snuk_item.h @@ -1,9 +1,9 @@ #pragma once #include "parser_common.h" -#include "snuk/snuk_error.h" #include "snuk/darray.h" #include "snuk/defines.h" +#include "snuk/snuk_error.h" #include "snuk/string_view.h" #include "snuk_type.h" diff --git a/include/snuk/snuk_error.h b/include/snuk/snuk_error.h index 0ca3986..6d930dc 100644 --- a/include/snuk/snuk_error.h +++ b/include/snuk/snuk_error.h @@ -4,8 +4,8 @@ typedef struct { const char *file; // filename or NULL - uint32_t line; // 1-indexed - uint32_t col; // 1-indexed + uint32_t line; // 1-indexed + uint32_t col; // 1-indexed } SnukSrcLoc; #define SNUK_SRC_LOC_NULL ((SnukSrcLoc){NULL, 0, 0}) @@ -18,13 +18,13 @@ typedef enum { typedef enum { SNUK_INTERP_ERR_NONE = 0, - SNUK_INTERP_ERR_TYPE, // type system violations - SNUK_INTERP_ERR_NAME, // name resolution / declaration conflicts - SNUK_INTERP_ERR_FUNCALL, // function call errors - SNUK_INTERP_ERR_ASSIGN, // assignment / initialization failures - SNUK_INTERP_ERR_CONTROL_FLOW, // break/continue/return outside valid scope - SNUK_INTERP_ERR_INTERFACE, // interface creation errors - SNUK_INTERP_ERR_INTERNAL, // unexpected/unreachable code paths + SNUK_INTERP_ERR_TYPE, // type system violations + SNUK_INTERP_ERR_NAME, // name resolution / declaration conflicts + SNUK_INTERP_ERR_FUNCALL, // function call errors + SNUK_INTERP_ERR_ASSIGN, // assignment / initialization failures + SNUK_INTERP_ERR_CONTROL_FLOW, // break/continue/return outside valid scope + SNUK_INTERP_ERR_INTERFACE, // interface creation errors + SNUK_INTERP_ERR_INTERNAL, // unexpected/unreachable code paths } SnukInterpError; typedef enum { @@ -48,8 +48,8 @@ typedef enum { typedef struct { SnukErrorKind kind; - uint32_t code; // cast from SnukInterpError or SnukParseError - const char *msg; // human-readable description + uint32_t code; // cast from SnukInterpError or SnukParseError + const char *msg; // human-readable description SnukSrcLoc loc; } SnukError; @@ -57,14 +57,22 @@ typedef struct { static inline const char *snuk_interp_error_msg(SnukInterpError code) { switch (code) { - case SNUK_INTERP_ERR_NONE: return "no error"; - case SNUK_INTERP_ERR_TYPE: return "type error"; - case SNUK_INTERP_ERR_NAME: return "name error"; - case SNUK_INTERP_ERR_FUNCALL: return "function call error"; - case SNUK_INTERP_ERR_ASSIGN: return "assignment error"; - case SNUK_INTERP_ERR_CONTROL_FLOW: return "control flow error"; - case SNUK_INTERP_ERR_INTERFACE: return "interface error"; - case SNUK_INTERP_ERR_INTERNAL: return "internal error"; + case SNUK_INTERP_ERR_NONE: + return "no error"; + case SNUK_INTERP_ERR_TYPE: + return "type error"; + case SNUK_INTERP_ERR_NAME: + return "name error"; + case SNUK_INTERP_ERR_FUNCALL: + return "function call error"; + case SNUK_INTERP_ERR_ASSIGN: + return "assignment error"; + case SNUK_INTERP_ERR_CONTROL_FLOW: + return "control flow error"; + case SNUK_INTERP_ERR_INTERFACE: + return "interface error"; + case SNUK_INTERP_ERR_INTERNAL: + return "internal error"; } return "unknown error"; } diff --git a/repl/runtime.c b/repl/runtime.c index b24aa26..097939d 100644 --- a/repl/runtime.c +++ b/repl/runtime.c @@ -15,7 +15,8 @@ void snuk_runtime_execute(Runtime *rt, const char *src) { SnukError parse_err = snuk_parser_clear_error(&parser); if (parse_err.kind != SNUK_ERROR_KIND_NONE) { - log_error("[Error] at line %u, col %u: %s", parse_err.loc.line, parse_err.loc.col, parse_err.msg); + log_error("[Error] at line %u, col %u: %s", parse_err.loc.line, parse_err.loc.col, + parse_err.msg); continue; } @@ -24,8 +25,7 @@ void snuk_runtime_execute(Runtime *rt, const char *src) { if (err.kind != SNUK_ERROR_KIND_NONE) { if (err.loc.line) log_error("[Error] at line %u, col %u: %s", err.loc.line, err.loc.col, err.msg); - else - log_error("[Error] %s", err.msg); + else log_error("[Error] %s", err.msg); } snuk_value_log(value); log_trace("", NULL); diff --git a/src/interpreter/interpreter.c b/src/interpreter/interpreter.c index 20eb1fe..5f540b1 100644 --- a/src/interpreter/interpreter.c +++ b/src/interpreter/interpreter.c @@ -207,7 +207,8 @@ SnukValue snuk_interpreter_exec_item(SnukInterpreter *intpret, SnukItem *item) { interpreter_clear_trash(intpret); SnukValue res = interpreter_exec_item(intpret, item, true); - if (intpret->signal != SNUK_SIGNAL_NONE) interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "break/continue/return outside of function or loop scope"); + if (intpret->signal != SNUK_SIGNAL_NONE) + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "break/continue/return outside of function or loop scope"); return res; } @@ -701,7 +702,8 @@ static SnukValue execute_for_expr(SnukInterpreter *intpret, SnukExpr *expr, bool if (expr->for_loop.init) { SnukValue val = interpreter_exec_item(intpret, expr->for_loop.init, false); if (intpret->signal != SNUK_SIGNAL_NONE) - interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "break/continue/return not allowed in for-loop initializer"); + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, + "break/continue/return not allowed in for-loop initializer"); snuk_value_free(val); } @@ -774,7 +776,8 @@ static SnukValue execute_type_declaration(SnukInterpreter *intpret, SnukExpr *ex SnukValue val = interpreter_exec_item(intpret, expr->type_expr.members[i], true); snuk_value_free(val); if (intpret->signal != SNUK_SIGNAL_NONE) - interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, "break/continue/return not allowed in type declaration body"); + interpreter_error(intpret, SNUK_INTERP_ERR_CONTROL_FLOW, + "break/continue/return not allowed in type declaration body"); } interpreter_pop_scope(intpret); @@ -830,10 +833,14 @@ static SnukValue execute_inst_creation(SnukInterpreter *intpret, SnukExpr *expr, // if builtin type, make sure value of value member is right SnukValueType val_type = snuk_builtins_get_value_type(value.type_value.type->name); if (val_type != SNUK_VALUE_UNKOWN && snuk_string_view_equal(name, value_str)) - if (val.type != val_type) interpreter_error_fmt(intpret, SNUK_INTERP_ERR_TYPE, "invalid value type for builtin member 'value': expected a different type"); + if (val.type != val_type) + interpreter_error_fmt( + intpret, SNUK_INTERP_ERR_TYPE, + "invalid value type for builtin member 'value': expected a different type"); if (!interpreter_set_member(intpret, value, name, val)) - interpreter_error_fmt(intpret, SNUK_INTERP_ERR_ASSIGN, "failed to initialize member '%.*s' in instance", (int)name.len, name.str); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_ASSIGN, + "failed to initialize member '%.*s' in instance", (int)name.len, name.str); snuk_value_free(val); } @@ -982,7 +989,9 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo uint64_t fn_param_count = snuk_darray_get_length(fn_scope->vars); uint64_t param_count = snuk_darray_get_length(expr->call.params); - if (fn_param_count < param_count) interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "function expects %llu argument(s) but %llu provided", (unsigned long long)fn_param_count, (unsigned long long)param_count); + if (fn_param_count < param_count) + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "function expects %llu argument(s) but %llu provided", + (unsigned long long)fn_param_count, (unsigned long long)param_count); bool named_params = false; for (uint64_t i = 0; i < param_count; ++i) { @@ -999,7 +1008,8 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo name = param->assign.identifier->identifier; fn_env = snuk_scope_lookup(fn_scope_rc, name, NULL); if (!fn_env) { - interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "unknown parameter '%.*s'", (int)name.len, name.str); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "unknown parameter '%.*s'", + (int)name.len, name.str); break; } type = fn_env->type; @@ -1015,8 +1025,8 @@ static SnukValue execute_call_expr(SnukInterpreter *intpret, SnukExpr *expr, boo SnukValue val = interpreter_eval_expr(intpret, value, true); if (!snuk_interpreter_create_env(intpret, name, type, val, false)) - interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, "failed to bind argument to parameter '%.*s'", - (int)name.len, name.str); + interpreter_error_fmt(intpret, SNUK_INTERP_ERR_FUNCALL, + "failed to bind argument to parameter '%.*s'", (int)name.len, name.str); snuk_value_free(val); } @@ -1386,8 +1396,10 @@ static SnukValue execute_member_get(SnukInterpreter *intpret, SnukExpr *expr, bo res.native_fn.instance = snuk_ref_counter_retain_weak(type_or_inst.type_value.closure); } - if (res.type == SNUK_VALUE_UNKOWN) interpreter_error_fmt(intpret, SNUK_INTERP_ERR_NAME, "type or instance has no member '%.*s'", - (int)expr->member_access.field->identifier.len, expr->member_access.field->identifier.str); + if (res.type == SNUK_VALUE_UNKOWN) + interpreter_error_fmt( + intpret, SNUK_INTERP_ERR_NAME, "type or instance has no member '%.*s'", + (int)expr->member_access.field->identifier.len, expr->member_access.field->identifier.str); interpreter_trash(intpret, type_or_inst); return res; diff --git a/src/parser/parser.c b/src/parser/parser.c index 4bd698a..e930543 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -12,7 +12,8 @@ void snuk_parser_init(SnukParser *parser, const char *src, SnukAllocator *alloca parser->previous = (SnukToken){0}; parser->current = snuk_lexer_next_token(&parser->lexer); - if (parser->current.type == SNUK_TOKEN_ERROR) parser_error(parser, SNUK_PARSE_ERR_LEXER_ERROR, "lexer error"); + if (parser->current.type == SNUK_TOKEN_ERROR) + parser_error(parser, SNUK_PARSE_ERR_LEXER_ERROR, "lexer error"); parser->next = snuk_lexer_next_token(&parser->lexer); } @@ -45,8 +46,7 @@ void parser_error(SnukParser *parser, SnukParseError code, const char *msg) { } SnukItem *parser_sync(SnukParser *parser) { - while (parser->current.type != SNUK_TOKEN_EOF - && parser->current.type != SNUK_TOKEN_SEMICOLON + while (parser->current.type != SNUK_TOKEN_EOF && parser->current.type != SNUK_TOKEN_SEMICOLON && parser->current.type != SNUK_TOKEN_VSEMICOLON) { parser_advance(parser); } diff --git a/src/parser/snuk_type.c b/src/parser/snuk_type.c index 3fab5ca..1039714 100644 --- a/src/parser/snuk_type.c +++ b/src/parser/snuk_type.c @@ -22,7 +22,8 @@ SnukType *snuk_type_parse_interface(SnukParser *parser) { SnukVar *var = snuk_var_parse(parser, false); parser_expect_item_end(parser); - if (var->value) parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "interface members should not have values"); + if (var->value) + parser_error(parser, SNUK_PARSE_ERR_UNEXPECTED_TOKEN, "interface members should not have values"); type = build_interface_type(parser, type, var); }