From 72162a741000d0011d15336f70695e6d639a22b6 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Sun, 18 Jan 2026 20:49:50 +1100 Subject: [PATCH 1/3] Start work on error handling --- src/interpreter.c | 33 ++++++++++++++++++++++++++++++ src/interpreter.h | 1 + src/types.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++ src/types.h | 22 +++++++++++++++++++- 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/interpreter.c b/src/interpreter.c index 9b04845..f7ddab9 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -11,6 +11,8 @@ int currentInstruction = 0; +bool isMainScopeGlobal = true; + [[noreturn]] void runtimeError(GroundRuntimeError error, char* what, GroundInstruction* where, int whereLine) { printf("Ground runtime error:\n ErrorType: "); switch (error) { @@ -78,6 +80,22 @@ int currentInstruction = 0; exit(1); } +[[noreturn]] void throwError(GroundError* error) { + printf("Uncaught Ground runtime error:\n ErrorType: %s\n", error->type); + if (error->what != NULL) { + printf(" ErrorContext: %s\n", error->what); + } + if (error->where != NULL) { + printf(" ErrorInstruction: "); + printGroundInstruction(error->where); + printf("\n"); + } + if (error->line > -1) { + printf(" ErrorLine: %zu\n", error->line + 1); + } + exit(1); +} + GroundLabel* findLabel(GroundLabel* head, const char *id) { GroundLabel *item; HASH_FIND_STR(head, id, item); @@ -438,6 +456,8 @@ GroundValue interpretGroundProgram(GroundProgram* in, GroundScope* inScope) { scope.labels = &labels; scope.variables = &variables; } + scope.isMainScope = isMainScopeGlobal; + isMainScopeGlobal = false; // Preprocess all labels, structs and functions for (size_t i = 0; i < in->size; i++) { if (in->instructions[i].type == CREATELABEL) { @@ -872,6 +892,10 @@ GroundValue interpretGroundInstruction(GroundInstruction inst, GroundScope* scop addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("none")); break; } + case ERROR: { + addVariable(scope->variables, in->args.args[1].value.refName, createStringGroundValue("error")); + break; + } } break; @@ -1737,6 +1761,12 @@ GroundValue interpretGroundInstruction(GroundInstruction inst, GroundScope* scop if (function->nativeFn) { GroundValue returnValue = function->nativeFn(scope, argsList); + if (returnValue.type == ERROR) { + if (scope->isMainScope) { + throwError(&returnValue.data.errorVal); + } + return returnValue; + } if (returnValue.type != function->returnType) { runtimeError(RETURN_TYPE_MISMATCH, "Unexpected return value type from native function", in, currentInstruction); } @@ -1756,6 +1786,9 @@ GroundValue interpretGroundInstruction(GroundInstruction inst, GroundScope* scop size_t currentCurrentInstruction = currentInstruction; currentInstruction = function->startLine; GroundValue returnValue = interpretGroundProgram(&function->program, &newScope); + if (returnValue.type == ERROR) { + return returnValue; + } if (returnValue.type != function->returnType) { runtimeError(RETURN_TYPE_MISMATCH, "Unexpected return value type from function", in, currentInstruction); } diff --git a/src/interpreter.h b/src/interpreter.h index a2df833..cde1fff 100644 --- a/src/interpreter.h +++ b/src/interpreter.h @@ -29,6 +29,7 @@ typedef struct GroundVariable { typedef struct GroundScope { GroundLabel** labels; GroundVariable** variables; + bool isMainScope; } GroundScope; typedef struct GroundDebugInstruction { diff --git a/src/types.c b/src/types.c index d5b294f..099e82d 100644 --- a/src/types.c +++ b/src/types.c @@ -51,6 +51,13 @@ GroundValue createFunctionGroundValue(GroundFunction* in) { return gv; } +GroundValue createErrorGroundValue(GroundError in) { + GroundValue gv; + gv.data.errorVal = in; + gv.type = ERROR; + return gv; +} + GroundValue createNoneGroundValue() { GroundValue gv; gv.type = NONE; @@ -168,6 +175,18 @@ void freeGroundValue(GroundValue* gv) { freeGroundStruct(gstruct); free(gstruct); } + if (gv->type == ERROR) { + GroundError* error = &gv->data.errorVal; + if (error->type != NULL) { + free(error->type); + } + if (error->what != NULL) { + free(error->what); + } + if (error->where != NULL) { + freeGroundInstruction(error->where); + } + } } GroundArg createValueGroundArg(GroundValue value) { @@ -529,3 +548,35 @@ void freeGroundObject(GroundObject* object) { } object->fields = NULL; } + +GroundError createGroundError(char* what, char* type, GroundInstruction* where, size_t* line) { + GroundError ge; + if (what != NULL) { + ge.what = malloc(strlen(what) + 1); + strcpy(ge.what, what); + } else { + ge.what = NULL; + } + + if (type != NULL) { + ge.type = malloc(strlen(type) + 1); + strcpy(ge.type, type); + } else { + ge.type = "GenericError"; + } + + if (where != NULL) { + ge.where = malloc(sizeof(GroundInstruction)); + *ge.where = copyGroundInstruction(where); + } else { + ge.where = NULL; + } + + if (line != NULL) { + ge.line = *line; + } else { + ge.line = -1; + } + + return ge; +} diff --git a/src/types.h b/src/types.h index b1803ee..aa7496b 100644 --- a/src/types.h +++ b/src/types.h @@ -13,7 +13,7 @@ typedef enum GroundInstType { } GroundInstType; typedef enum GroundValueType { - INT, DOUBLE, STRING, CHAR, BOOL, LIST, FUNCTION, STRUCTVAL, CUSTOM, NONE + INT, DOUBLE, STRING, CHAR, BOOL, LIST, FUNCTION, STRUCTVAL, CUSTOM, ERROR, NONE } GroundValueType; typedef enum GroundArgType { @@ -29,6 +29,7 @@ struct GroundFunction; struct GroundScope; struct GroundStruct; struct GroundObject; +struct GroundInstruction; struct List; @@ -42,6 +43,16 @@ typedef struct List { struct GroundValue* values; } List; +/* + * Stores data associated with an error thrown during Ground execution. + */ +typedef struct GroundError { + char* what; + char* type; + struct GroundInstruction* where; + size_t line; +} GroundError; + /* * Stores literal values created in a Ground program. * Associated functions: @@ -57,6 +68,7 @@ typedef struct GroundValue { char charVal; bool boolVal; List listVal; + GroundError errorVal; struct GroundFunction* fnVal; struct GroundStruct* structVal; struct GroundObject* customVal; @@ -187,6 +199,9 @@ GroundValue createListGroundValue(List in); // Creates a GroundValue conatining (in), with type FUNCTION. GroundValue createFunctionGroundValue(GroundFunction* in); +// Creates a GroundValue containing (in), with type ERROR. +GroundValue createErrorGroundValue(GroundError in); + // Creates a Groundvalue with type NONE. GroundValue createNoneGroundValue(); @@ -257,4 +272,9 @@ GroundObjectField* findField(GroundObject head, const char *id); // Frees a GroundObject void freeGroundObject(GroundObject* object); +// Creates a GroundError. +GroundError createGroundError(char* what, char* type, GroundInstruction* where, size_t* line); + + + #endif -- 2.47.3 From 754b63fb754b86c02df0a82b0fdacdd44f0161b9 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Sun, 18 Jan 2026 21:37:46 +1100 Subject: [PATCH 2/3] Update error handling inside extlibs --- libs/fileio/fileio.c | 6 +- libs/request/request.c | 136 +++++++++-------------------------------- src/interpreter.c | 5 +- src/types.c | 6 +- src/types.h | 1 + 5 files changed, 42 insertions(+), 112 deletions(-) diff --git a/libs/fileio/fileio.c b/libs/fileio/fileio.c index 3e576b2..7aeaeb5 100644 --- a/libs/fileio/fileio.c +++ b/libs/fileio/fileio.c @@ -10,7 +10,7 @@ GroundValue native_file_read(GroundScope* scope, List args) { char* path = args.values[0].data.stringVal; FILE* f = fopen(path, "r"); if (!f) { - return groundCreateValue(NONE); + ERROR("Failed to open file for reading", "FileError"); } fseek(f, 0, SEEK_END); @@ -20,7 +20,7 @@ GroundValue native_file_read(GroundScope* scope, List args) { char* content = malloc(fsize + 1); if (!content) { fclose(f); - return groundCreateValue(NONE); + ERROR("Failed to allocate memory for file reading", "FileError"); } fread(content, 1, fsize, f); @@ -42,7 +42,7 @@ GroundValue native_file_write(GroundScope* scope, List args) { FILE* f = fopen(path, "w"); if (!f) { - return groundCreateValue(BOOL, 0); + ERROR("Failed to open file for writing", "FileError"); } fprintf(f, "%s", content); diff --git a/libs/request/request.c b/libs/request/request.c index 7a50e38..b70645a 100644 --- a/libs/request/request.c +++ b/libs/request/request.c @@ -50,21 +50,11 @@ size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) { // GET request GroundValue get_request(GroundScope* scope, List args) { - if (args.size < 1) { - printf("Requires a URL argument\n"); - return groundCreateValue(NONE); - } - if (args.values[0].type != STRING) { - printf("Arg 1 needs to be a string (URL)\n"); - return groundCreateValue(NONE); - } - curl_setup(); CURL* handle = curl_easy_init(); if (!handle) { - printf("Curl failed to init\n"); - return groundCreateValue(NONE); + ERROR("CURL failed to init", "GenericRequestError"); } ResponseBuffer buffer = {0}; @@ -81,10 +71,9 @@ GroundValue get_request(GroundScope* scope, List args) { CURLcode result = curl_easy_perform(handle); if (result != CURLE_OK) { - printf("Curl request failed: %s\n", curl_easy_strerror(result)); - free(buffer.data); - curl_easy_cleanup(handle); - return groundCreateValue(NONE); + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result)); + ERROR(buf, "ActionRequestError"); } curl_easy_cleanup(handle); @@ -95,21 +84,11 @@ GroundValue get_request(GroundScope* scope, List args) { // POST request GroundValue post_request(GroundScope* scope, List args) { - if (args.size < 2) { - printf("Requires URL and POST data arguments\n"); - return groundCreateValue(NONE); - } - if (args.values[0].type != STRING || args.values[1].type != STRING) { - printf("Both arguments need to be strings (URL, POST data)\n"); - return groundCreateValue(NONE); - } - curl_setup(); CURL* handle = curl_easy_init(); if (!handle) { - printf("Curl failed to init\n"); - return groundCreateValue(NONE); + ERROR("CURL failed to init", "GenericRequestError"); } ResponseBuffer buffer = {0}; @@ -127,10 +106,11 @@ GroundValue post_request(GroundScope* scope, List args) { CURLcode result = curl_easy_perform(handle); if (result != CURLE_OK) { - printf("Curl POST request failed: %s\n", curl_easy_strerror(result)); free(buffer.data); + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result)); curl_easy_cleanup(handle); - return groundCreateValue(NONE); + ERROR(buf, "ActionRequestError"); } curl_easy_cleanup(handle); @@ -153,27 +133,16 @@ int find_ws_slot() { // WebSocket Connect - returns connection ID as INT GroundValue ws_connect(GroundScope* scope, List args) { - if (args.size < 1) { - printf("Requires a WebSocket URL argument (ws:// or wss://)\n"); - return groundCreateValue(NONE); - } - if (args.values[0].type != STRING) { - printf("Arg 1 needs to be a string (WebSocket URL)\n"); - return groundCreateValue(NONE); - } - curl_setup(); int slot = find_ws_slot(); if (slot == -1) { - printf("Maximum WebSocket connections reached\n"); - return groundCreateValue(NONE); + ERROR("Maximum WebSocket connections reached", "OverflowError"); } CURL* handle = curl_easy_init(); if (!handle) { - printf("Curl failed to init\n"); - return groundCreateValue(NONE); + ERROR("CURL failed to init", "GenericWSError"); } curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal); @@ -183,9 +152,10 @@ GroundValue ws_connect(GroundScope* scope, List args) { CURLcode result = curl_easy_perform(handle); if (result != CURLE_OK) { - printf("WebSocket connection failed: %s\n", curl_easy_strerror(result)); + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "Curl WebSocket failed: %s\n", curl_easy_strerror(result)); curl_easy_cleanup(handle); - return groundCreateValue(NONE); + ERROR(buf, "ActionWSError"); } ws_connections[slot].handle = handle; @@ -197,19 +167,9 @@ GroundValue ws_connect(GroundScope* scope, List args) { // WebSocket Send - sends a text message, returns bytes sent as INT GroundValue ws_send(GroundScope* scope, List args) { - if (args.size < 2) { - printf("Requires connection ID and message\n"); - return groundCreateValue(NONE); - } - if (args.values[0].type != INT || args.values[1].type != STRING) { - printf("Args need to be (INT connection ID, STRING message)\n"); - return groundCreateValue(NONE); - } - int conn_id = (int)args.values[0].data.intVal; if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - printf("Invalid or closed WebSocket connection\n"); - return groundCreateValue(NONE); + ERROR("Invalid WebSocket connection ID", "GenericWSError"); } CURL* handle = ws_connections[conn_id].handle; @@ -219,8 +179,9 @@ GroundValue ws_send(GroundScope* scope, List args) { CURLcode result = curl_ws_send(handle, message, strlen(message), &sent, 0, CURLWS_TEXT); if (result != CURLE_OK) { - printf("WebSocket send failed: %s\n", curl_easy_strerror(result)); - return groundCreateValue(NONE); + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "WebSocket send failed: %s\n", curl_easy_strerror(result)); + ERROR(buf, "ActionWSError"); } return groundCreateValue(INT, (int64_t)sent); @@ -228,19 +189,9 @@ GroundValue ws_send(GroundScope* scope, List args) { // WebSocket Receive - receives a message (blocking) GroundValue ws_receive(GroundScope* scope, List args) { - if (args.size < 1) { - printf("Requires connection ID\n"); - return groundCreateValue(NONE); - } - if (args.values[0].type != INT) { - printf("Arg 1 needs to be an INT (connection ID)\n"); - return groundCreateValue(NONE); - } - int conn_id = (int)args.values[0].data.intVal; if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - printf("Invalid or closed WebSocket connection\n"); - return groundCreateValue(NONE); + ERROR("Invalid WebSocket connection ID", "GenericWSError"); } CURL* handle = ws_connections[conn_id].handle; @@ -255,8 +206,9 @@ GroundValue ws_receive(GroundScope* scope, List args) { // No data available right now return groundCreateValue(STRING, ""); } - printf("WebSocket receive failed: %s\n", curl_easy_strerror(result)); - return groundCreateValue(NONE); + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "WebSocket receive failed: %s\n", curl_easy_strerror(result)); + ERROR(buf, "ActionWSError"); } buffer[received] = '\0'; @@ -283,21 +235,11 @@ GroundValue ws_receive(GroundScope* scope, List args) { // WebSocket Receive with timeout (non-blocking version) GroundValue ws_receive_timeout(GroundScope* scope, List args) { - if (args.size < 2) { - printf("Requires connection ID and timeout in milliseconds\n"); - return groundCreateValue(NONE); - } - if (args.values[0].type != INT || args.values[1].type != INT) { - printf("Both arguments need to be INT (connection ID, timeout_ms)\n"); - return groundCreateValue(NONE); - } - int conn_id = (int)args.values[0].data.intVal; long timeout_ms = (long)args.values[1].data.intVal; if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - printf("Invalid or closed WebSocket connection\n"); - return groundCreateValue(NONE); + ERROR("Invalid WebSocket connection ID", "GenericWSError"); } CURL* handle = ws_connections[conn_id].handle; @@ -316,8 +258,9 @@ GroundValue ws_receive_timeout(GroundScope* scope, List args) { } if (result != CURLE_OK) { - printf("WebSocket receive failed: %s\n", curl_easy_strerror(result)); - return groundCreateValue(NONE); + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "WebSocket receive failed: %s\n", curl_easy_strerror(result)); + ERROR(buf, "ActionWSError"); } buffer[received] = '\0'; @@ -341,19 +284,9 @@ GroundValue ws_receive_timeout(GroundScope* scope, List args) { // WebSocket Close - properly close a connection, returns BOOL success GroundValue ws_close(GroundScope* scope, List args) { - if (args.size < 1) { - printf("Requires connection ID\n"); - return groundCreateValue(NONE); - } - if (args.values[0].type != INT) { - printf("Arg 1 needs to be an INT (connection ID)\n"); - return groundCreateValue(NONE); - } - int conn_id = (int)args.values[0].data.intVal; if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - printf("Invalid or already closed WebSocket connection\n"); - return groundCreateValue(BOOL, false); + ERROR("Invalid or already connected websocket", "GenericWSError"); } CURL* handle = ws_connections[conn_id].handle; @@ -374,19 +307,9 @@ GroundValue ws_close(GroundScope* scope, List args) { // WebSocket Send Binary data, returns bytes sent as INT GroundValue ws_send_binary(GroundScope* scope, List args) { - if (args.size < 2) { - printf("Requires connection ID and binary data\n"); - return groundCreateValue(NONE); - } - if (args.values[0].type != INT || args.values[1].type != STRING) { - printf("Args need to be (INT connection ID, STRING binary data)\n"); - return groundCreateValue(NONE); - } - int conn_id = (int)args.values[0].data.intVal; if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) { - printf("Invalid or closed WebSocket connection\n"); - return groundCreateValue(NONE); + ERROR("Invalid WebSocket connection ID", "GenericWSError"); } CURL* handle = ws_connections[conn_id].handle; @@ -396,8 +319,9 @@ GroundValue ws_send_binary(GroundScope* scope, List args) { CURLcode result = curl_ws_send(handle, data, strlen(data), &sent, 0, CURLWS_BINARY); if (result != CURLE_OK) { - printf("WebSocket binary send failed: %s\n", curl_easy_strerror(result)); - return groundCreateValue(NONE); + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "WebSocket binary send failed: %s\n", curl_easy_strerror(result)); + ERROR(buf, "ActionWSError"); } return groundCreateValue(INT, (int64_t)sent); diff --git a/src/interpreter.c b/src/interpreter.c index f7ddab9..5e6ac0a 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -90,7 +90,7 @@ bool isMainScopeGlobal = true; printGroundInstruction(error->where); printf("\n"); } - if (error->line > -1) { + if (error->hasLine) { printf(" ErrorLine: %zu\n", error->line + 1); } exit(1); @@ -1762,6 +1762,9 @@ GroundValue interpretGroundInstruction(GroundInstruction inst, GroundScope* scop if (function->nativeFn) { GroundValue returnValue = function->nativeFn(scope, argsList); if (returnValue.type == ERROR) { + returnValue.data.errorVal.line = currentInstruction; + returnValue.data.errorVal.hasLine = true; + returnValue.data.errorVal.where = in; if (scope->isMainScope) { throwError(&returnValue.data.errorVal); } diff --git a/src/types.c b/src/types.c index 099e82d..01becb4 100644 --- a/src/types.c +++ b/src/types.c @@ -562,7 +562,8 @@ GroundError createGroundError(char* what, char* type, GroundInstruction* where, ge.type = malloc(strlen(type) + 1); strcpy(ge.type, type); } else { - ge.type = "GenericError"; + ge.type = malloc(strlen("GenericError") + 1); + strcpy(ge.type, "GenericError"); } if (where != NULL) { @@ -575,7 +576,8 @@ GroundError createGroundError(char* what, char* type, GroundInstruction* where, if (line != NULL) { ge.line = *line; } else { - ge.line = -1; + ge.line = 0; + ge.hasLine = false; } return ge; diff --git a/src/types.h b/src/types.h index aa7496b..0b3c903 100644 --- a/src/types.h +++ b/src/types.h @@ -51,6 +51,7 @@ typedef struct GroundError { char* type; struct GroundInstruction* where; size_t line; + bool hasLine; } GroundError; /* -- 2.47.3 From 054af1631c402650205b72434b26f41ae8e0f6fa Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Mon, 19 Jan 2026 19:29:14 +1100 Subject: [PATCH 3/3] I forgot to push header file --- include/groundext.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/groundext.h b/include/groundext.h index e8c35ac..735b9b7 100644 --- a/include/groundext.h +++ b/include/groundext.h @@ -7,10 +7,30 @@ extern "C" { #endif +// Macro for easily returning errors +#define ERROR(what, type) return createErrorGroundValue(createGroundError(what, type, NULL, NULL)) + // Forward declaration of the scope structure used in interpreter struct GroundScope; typedef struct GroundScope GroundScope; +/* + * Stores data associated with an error thrown during Ground execution. + */ +typedef struct GroundError { + char* what; + char* type; + struct GroundInstruction* where; + size_t line; + bool hasLine; +} GroundError; + +// Creates a GroundValue containing (in), with type ERROR. +GroundValue createErrorGroundValue(GroundError in); + +// Creates a GroundError. +GroundError createGroundError(char* what, char* type, GroundInstruction* where, size_t* line); + /* * Function pointer type for native functions. * scope: The current execution scope (opaque). -- 2.47.3