From 75d27afe2cc4283e6b0f7e57182e0d1df5e88fc6 Mon Sep 17 00:00:00 2001 From: CPunch Date: Mon, 28 Aug 2023 21:13:00 -0500 Subject: [PATCH] WIP: major error handling refactoring switching to setjmp instead of the really bad global 'panic' flag --- src/clex.c | 9 +++---- src/clex.h | 4 +-- src/cparse.c | 73 ++++++++-------------------------------------------- src/cstate.c | 16 ++++++++++++ src/cstate.h | 17 ++++++++++-- src/cvm.c | 69 +++++++++++++++++++++++++++---------------------- src/cvm.h | 2 +- 7 files changed, 86 insertions(+), 104 deletions(-) diff --git a/src/clex.c b/src/clex.c index fbf0b3d..811d48e 100644 --- a/src/clex.c +++ b/src/clex.c @@ -389,9 +389,8 @@ static CToken parseIdentifier(CLexState *state) return makeToken(state, identifierType(state)); // is it a reserved word? } -CLexState *cosmoL_newLexState(CState *cstate, const char *source) +void cosmoL_initLexState(CState *cstate, CLexState *state, const char *source) { - CLexState *state = cosmoM_xmalloc(cstate, sizeof(CLexState)); state->startChar = (char *)source; state->currentChar = (char *)source; state->line = 1; @@ -400,13 +399,11 @@ CLexState *cosmoL_newLexState(CState *cstate, const char *source) state->cstate = cstate; resetBuffer(state); - - return state; } -void cosmoL_freeLexState(CState *state, CLexState *lstate) +void cosmoL_cleanupLexState(CState *state, CLexState *lstate) { - cosmoM_free(state, CLexState, lstate); + // stubbed } CToken cosmoL_scanToken(CLexState *state) diff --git a/src/clex.h b/src/clex.h index dab608e..a8d9ead 100644 --- a/src/clex.h +++ b/src/clex.h @@ -103,8 +103,8 @@ typedef struct CState *cstate; } CLexState; -CLexState *cosmoL_newLexState(CState *state, const char *source); -void cosmoL_freeLexState(CState *state, CLexState *lstate); +void cosmoL_initLexState(CState *cstate, CLexState *state, const char *source); +void cosmoL_cleanupLexState(CState *state, CLexState *lstate); CToken cosmoL_scanToken(CLexState *state); diff --git a/src/cparse.c b/src/cparse.c index dc578c3..c7099a3 100644 --- a/src/cparse.c +++ b/src/cparse.c @@ -59,14 +59,12 @@ typedef struct CCompilerState typedef struct { + CLexState lex; CState *state; - CLexState *lex; CCompilerState *compiler; CObjString *module; // name of the module CToken current; CToken previous; // token right after the current token - bool hadError; - bool panic; } CParseState; typedef enum @@ -124,6 +122,8 @@ static void initCompilerState(CParseState *pstate, CCompilerState *ccstate, Func ccstate->function = cosmoO_newFunction(pstate->state); ccstate->function->module = pstate->module; + cosmoV_pushRef(pstate->state, (CObj *)ccstate->function); + ccstate->loop.scope = -1; // there is no loop yet if (type != FTYPE_SCRIPT) { @@ -146,11 +146,9 @@ static void initCompilerState(CParseState *pstate, CCompilerState *ccstate, Func static void initParseState(CParseState *pstate, CCompilerState *ccstate, CState *s, const char *source, const char *module) { - pstate->lex = cosmoL_newLexState(s, source); + cosmoL_initLexState(s, &pstate->lex, source); pstate->state = s; - pstate->hadError = false; - pstate->panic = false; pstate->compiler = ccstate; pstate->module = cosmoO_copyString(s, module, strlen(module)); @@ -159,14 +157,11 @@ static void initParseState(CParseState *pstate, CCompilerState *ccstate, CState static void freeParseState(CParseState *pstate) { - cosmoL_freeLexState(pstate->state, pstate->lex); + cosmoL_cleanupLexState(pstate->state, &pstate->lex); } static void errorAt(CParseState *pstate, CToken *token, const char *format, va_list args) { - if (pstate->hadError) - return; - if (token->type == TOKEN_EOF) { cosmoV_pushString(pstate->state, "At end: "); } else if (!(token->type == TOKEN_ERROR)) { @@ -177,14 +172,9 @@ static void errorAt(CParseState *pstate, CToken *token, const char *format, va_l cosmoO_pushVFString(pstate->state, format, args); - cosmoV_concat(pstate->state, 2); // concats the two strings together - - CObjError *err = cosmoV_throw(pstate->state); - err->line = token->line; - err->parserError = true; - - pstate->hadError = true; - pstate->panic = true; + // throw complete error string + cosmoV_concat(pstate->state, 2); + cosmoV_throw(pstate->state); } static void errorAtCurrent(CParseState *pstate, const char *format, ...) @@ -206,7 +196,7 @@ static void error(CParseState *pstate, const char *format, ...) static void advance(CParseState *pstate) { pstate->previous = pstate->current; - pstate->current = cosmoL_scanToken(pstate->lex); + pstate->current = cosmoL_scanToken(&pstate->lex); if (pstate->current.type == TOKEN_ERROR) { errorAtCurrent(pstate, pstate->current.start); @@ -280,7 +270,6 @@ uint16_t makeConstant(CParseState *pstate, CValue val) int indx = addConstant(pstate->state, getChunk(pstate), val); if (indx > UINT16_MAX) { error(pstate, "UInt overflow! Too many constants in one chunk!"); - return 0; } return (uint16_t)indx; @@ -350,7 +339,6 @@ static void addLocal(CParseState *pstate, CToken name) { if (pstate->compiler->localCount > UINT8_MAX) { error(pstate, "UInt overflow! Too many locals in scope!"); - return; } Local *local = &pstate->compiler->locals[pstate->compiler->localCount++]; @@ -365,7 +353,6 @@ static int addUpvalue(CParseState *pstate, CCompilerState *ccstate, uint8_t indx if (upvals > UINT8_MAX) { error(pstate, "UInt overflow! Too many upvalues in scope!"); - return -1; } // check and make sure we haven't already captured it @@ -768,7 +755,6 @@ static void table(CParseState *pstate, bool canAssign, Precedence prec) tblType = 1; // array-like } else { error(pstate, "Can't change table description type mid-definition!"); - return; } entries++; @@ -818,7 +804,7 @@ static void object(CParseState *pstate, bool canAssign, Precedence prec) // "pop" the 1 value valuePopped(pstate, 1); entries++; - } while (match(pstate, TOKEN_COMMA) && !pstate->hadError); + } while (match(pstate, TOKEN_COMMA)); consume(pstate, TOKEN_RIGHT_BRACE, "Expected '}' to end object definition."); } @@ -1158,9 +1144,6 @@ static uint16_t parseVariable(CParseState *pstate, const char *errorMessage, boo static void defineVariable(CParseState *pstate, uint16_t global, bool forceLocal) { - if (pstate->hadError) - return; - if (pstate->compiler->scopeDepth > 0 || forceLocal) { markInitialized(pstate, global); valuePopped(pstate, 1); // the local stays on the stack! @@ -1177,7 +1160,7 @@ static void _proto(CParseState *pstate) { int entries = 0; - while (!match(pstate, TOKEN_END) && !match(pstate, TOKEN_EOF) && !pstate->hadError) { + while (!match(pstate, TOKEN_END) && !match(pstate, TOKEN_EOF)) { if (match(pstate, TOKEN_FUNC)) { // define method consume(pstate, TOKEN_IDENTIFIER, "Expected identifier for method!"); @@ -1224,9 +1207,6 @@ static void localProto(CParseState *pstate) static void popLocals(CParseState *pstate, int toScope) { - if (pstate->hadError) - return; - // count the locals in scope to pop int localsToPop = 0; @@ -1686,18 +1666,6 @@ static void continueStatement(CParseState *pstate) writeJmpBack(pstate, pstate->compiler->loop.startBytecode); } -static void synchronize(CParseState *pstate) -{ - pstate->panic = false; - - while (pstate->current.type != TOKEN_EOF) { - if (pstate->previous.type == TOKEN_EOS) - return; - - advance(pstate); - } -} - static int expressionPrecedence(CParseState *pstate, int needed, Precedence prec, bool forceNeeded) { int lastExpected = pstate->compiler->expectedValues; @@ -1787,10 +1755,6 @@ static void statement(CParseState *pstate) static void declaration(CParseState *pstate) { statement(pstate); - - // if we paniced, skip the whole statement! - if (pstate->panic) - synchronize(pstate); } static CObjFunction *endCompiler(CParseState *pstate) @@ -1812,7 +1776,6 @@ CObjFunction *cosmoP_compileString(CState *state, const char *source, const char { CParseState parser; CCompilerState compiler; - cosmoM_freezeGC(state); // ignore all GC events while compiling initParseState(&parser, &compiler, state, source, module); advance(&parser); @@ -1825,24 +1788,10 @@ CObjFunction *cosmoP_compileString(CState *state, const char *source, const char popLocals(&parser, 0); - if (parser.hadError) { // we don't free the function, the state already has a reference to it in - // it's linked list of objects! - endCompiler(&parser); - freeParseState(&parser); - - cosmoM_unfreezeGC(state); - return NULL; - } - CObjFunction *resFunc = compiler.function; // finally free out parser states endCompiler(&parser); freeParseState(&parser); - - // push the funciton onto the stack so if we cause an GC event, it won't be free'd - cosmoV_pushRef(state, (CObj *)resFunc); - cosmoM_unfreezeGC(state); - cosmoV_pop(state); return resFunc; } diff --git a/src/cstate.c b/src/cstate.c index 6b241b8..4b4cccb 100644 --- a/src/cstate.c +++ b/src/cstate.c @@ -7,6 +7,21 @@ #include +CPanic *cosmoV_newPanic(CState *state) +{ + CPanic *panic = cosmoM_xmalloc(state, sizeof(CPanic)); + panic->prev = state->panic; + state->panic = panic; + return panic; +} + +void cosmoV_freePanic(CState *state) +{ + CPanic *panic = state->panic; + state->panic = panic->prev; + cosmoM_free(state, CPanic, panic); +} + CState *cosmoV_newState() { // we use C's malloc because we don't want to trigger a GC with an invalid state @@ -19,6 +34,7 @@ CState *cosmoV_newState() state->panic = false; state->freezeGC = 1; // we start frozen + state->panic = NULL; // GC state->objects = NULL; diff --git a/src/cstate.h b/src/cstate.h index 711d62f..d971e4f 100644 --- a/src/cstate.h +++ b/src/cstate.h @@ -6,6 +6,8 @@ #include "ctable.h" #include "cvalue.h" +#include + struct CCallFrame { CObjClosure *closure; @@ -38,11 +40,17 @@ typedef struct ArrayCObj int capacity; } ArrayCObj; +typedef struct CPanic +{ + jmp_buf jmp; + struct CPanic *prev; +} CPanic; + struct CState { - bool panic; int freezeGC; // when > 0, GC events will be ignored (for internal use) int frameCount; + CPanic *panic; CObjError *error; // NULL, unless panic is true CObj *objects; // tracks all of our allocated objects @@ -64,10 +72,15 @@ struct CState CValue stack[STACK_MAX]; // stack }; +CPanic *cosmoV_newPanic(CState *state); +void cosmoV_freePanic(CState *state); + COSMO_API CState *cosmoV_newState(); +COSMO_API void cosmoV_freeState(CState *state); + // expects 2*pairs values on the stack, each pair should consist of 1 key and 1 value COSMO_API void cosmoV_register(CState *state, int pairs); -COSMO_API void cosmoV_freeState(CState *state); + COSMO_API void cosmoV_printStack(CState *state); #endif diff --git a/src/cvm.c b/src/cvm.c index 865e634..e055867 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -10,6 +10,18 @@ #include #include +bool cosmoV_protect(CState *state) +{ + CPanic *panic = cosmoV_newPanic(state); + + if (setjmp(panic->jmp) == 0) { + return 1; + } + + cosmoV_freePanic(state); + return 0; +} + COSMO_API void cosmoV_pushFString(CState *state, const char *format, ...) { va_list args; @@ -57,21 +69,20 @@ COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char * { CObjFunction *func; - if ((func = cosmoP_compileString(state, src, name)) != NULL) { - // success + if (cosmoV_protect(state)) { + if ((func = cosmoP_compileString(state, src, name)) != NULL) { + // success #ifdef VM_DEBUG - disasmChunk(&func->chunk, func->module->str, 0); + disasmChunk(&func->chunk, func->module->str, 0); #endif - // push function onto the stack so it doesn't it cleaned up by the GC, at the same stack - // location put our closure - cosmoV_pushRef(state, (CObj *)func); - *(cosmoV_getTop(state, 0)) = cosmoV_newRef(cosmoO_newClosure(state, func)); - return true; + // push function onto the stack so it doesn't it cleaned up by the GC, at the same stack + // location put our closure + cosmoV_pushRef(state, (CObj *)func); + *(cosmoV_getTop(state, 0)) = cosmoV_newRef(cosmoO_newClosure(state, func)); + return true; + } } - // fail recovery - state->panic = false; - cosmoV_pushRef(state, (CObj *)state->error); return false; } @@ -109,30 +120,26 @@ COSMO_API void cosmoV_printError(CState *state, CObjError *err) } /* - takes value on top of the stack and wraps an CObjError around it, state->error is set to that - value the value on the stack is *expected* to be a string, but not required, so yes, this means - you could throw a nil value if you really wanted too.. + takes value on top of the stack and wraps an CObjError around it, then throws it */ -CObjError *cosmoV_throw(CState *state) +void cosmoV_throw(CState *state) { StkPtr temp = cosmoV_getTop(state, 0); - CObjError *error = cosmoO_newError(state, *temp); - state->error = error; - state->panic = true; - cosmoV_pop(state); // pops thrown value off the stack - return error; + // replace the value on the stack with the error + *temp = cosmoV_newRef((CObj *)cosmoO_newError(state, *temp)); + if (state->panic) { + longjmp(state->panic->jmp, 1); + } else { + fprintf(stderr, "Unhandled panic! "); + cosmoV_printError(state, error); + exit(1); + } } void cosmoV_error(CState *state, const char *format, ...) { - if (state->panic) - return; - - // i set panic before calling cosmoO_pushVFString, since that can also call cosmoV_error - state->panic = true; - // format the error string and push it onto the stack va_list args; va_start(args, format); @@ -422,9 +429,11 @@ bool invokeMethod(CState *state, CObj *obj, CValue func, int args, int nresults, // returns false if function call failed, true if function call succeeded bool cosmoV_pcall(CState *state, int args, int nresults) { - StkPtr base = cosmoV_getTop(state, args); - - if (!callCValue(state, *base, args, nresults, 0)) { + if (cosmoV_protect(state)) { + cosmoV_call(state, args, nresults); + return true; + } else { + printf("caught panic!\n"); // restore panic state state->panic = false; @@ -438,8 +447,6 @@ bool cosmoV_pcall(CState *state, int args, int nresults) return false; } - - return true; } // calls a callable object at stack->top - args - 1, passing the # of args to the callable, and diff --git a/src/cvm.h b/src/cvm.h index 96fab8a..e721184 100644 --- a/src/cvm.h +++ b/src/cvm.h @@ -31,7 +31,7 @@ COSMO_API void cosmoV_makeTable(CState *state, int pairs); COSMO_API void cosmoV_concat(CState *state, int vals); COSMO_API void cosmoV_pushFString(CState *state, const char *format, ...); COSMO_API void cosmoV_printError(CState *state, CObjError *err); -COSMO_API CObjError *cosmoV_throw(CState *state); +COSMO_API void cosmoV_throw(CState *state); COSMO_API void cosmoV_error(CState *state, const char *format, ...); COSMO_API void cosmo_insert(CState *state, int indx, CValue val);