WIP: major error handling refactoring

switching to setjmp instead of the really bad global 'panic' flag
This commit is contained in:
CPunch 2023-08-28 21:13:00 -05:00 committed by cpunch
parent 3f39211081
commit 75d27afe2c
7 changed files with 86 additions and 104 deletions

View File

@ -389,9 +389,8 @@ static CToken parseIdentifier(CLexState *state)
return makeToken(state, identifierType(state)); // is it a reserved word? 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->startChar = (char *)source;
state->currentChar = (char *)source; state->currentChar = (char *)source;
state->line = 1; state->line = 1;
@ -400,13 +399,11 @@ CLexState *cosmoL_newLexState(CState *cstate, const char *source)
state->cstate = cstate; state->cstate = cstate;
resetBuffer(state); 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) CToken cosmoL_scanToken(CLexState *state)

View File

@ -103,8 +103,8 @@ typedef struct
CState *cstate; CState *cstate;
} CLexState; } CLexState;
CLexState *cosmoL_newLexState(CState *state, const char *source); void cosmoL_initLexState(CState *cstate, CLexState *state, const char *source);
void cosmoL_freeLexState(CState *state, CLexState *lstate); void cosmoL_cleanupLexState(CState *state, CLexState *lstate);
CToken cosmoL_scanToken(CLexState *state); CToken cosmoL_scanToken(CLexState *state);

View File

@ -59,14 +59,12 @@ typedef struct CCompilerState
typedef struct typedef struct
{ {
CLexState lex;
CState *state; CState *state;
CLexState *lex;
CCompilerState *compiler; CCompilerState *compiler;
CObjString *module; // name of the module CObjString *module; // name of the module
CToken current; CToken current;
CToken previous; // token right after the current token CToken previous; // token right after the current token
bool hadError;
bool panic;
} CParseState; } CParseState;
typedef enum typedef enum
@ -124,6 +122,8 @@ static void initCompilerState(CParseState *pstate, CCompilerState *ccstate, Func
ccstate->function = cosmoO_newFunction(pstate->state); ccstate->function = cosmoO_newFunction(pstate->state);
ccstate->function->module = pstate->module; ccstate->function->module = pstate->module;
cosmoV_pushRef(pstate->state, (CObj *)ccstate->function);
ccstate->loop.scope = -1; // there is no loop yet ccstate->loop.scope = -1; // there is no loop yet
if (type != FTYPE_SCRIPT) { 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, static void initParseState(CParseState *pstate, CCompilerState *ccstate, CState *s,
const char *source, const char *module) const char *source, const char *module)
{ {
pstate->lex = cosmoL_newLexState(s, source); cosmoL_initLexState(s, &pstate->lex, source);
pstate->state = s; pstate->state = s;
pstate->hadError = false;
pstate->panic = false;
pstate->compiler = ccstate; pstate->compiler = ccstate;
pstate->module = cosmoO_copyString(s, module, strlen(module)); 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) 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) static void errorAt(CParseState *pstate, CToken *token, const char *format, va_list args)
{ {
if (pstate->hadError)
return;
if (token->type == TOKEN_EOF) { if (token->type == TOKEN_EOF) {
cosmoV_pushString(pstate->state, "At end: "); cosmoV_pushString(pstate->state, "At end: ");
} else if (!(token->type == TOKEN_ERROR)) { } 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); cosmoO_pushVFString(pstate->state, format, args);
cosmoV_concat(pstate->state, 2); // concats the two strings together // throw complete error string
cosmoV_concat(pstate->state, 2);
CObjError *err = cosmoV_throw(pstate->state); cosmoV_throw(pstate->state);
err->line = token->line;
err->parserError = true;
pstate->hadError = true;
pstate->panic = true;
} }
static void errorAtCurrent(CParseState *pstate, const char *format, ...) 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) static void advance(CParseState *pstate)
{ {
pstate->previous = pstate->current; pstate->previous = pstate->current;
pstate->current = cosmoL_scanToken(pstate->lex); pstate->current = cosmoL_scanToken(&pstate->lex);
if (pstate->current.type == TOKEN_ERROR) { if (pstate->current.type == TOKEN_ERROR) {
errorAtCurrent(pstate, pstate->current.start); errorAtCurrent(pstate, pstate->current.start);
@ -280,7 +270,6 @@ uint16_t makeConstant(CParseState *pstate, CValue val)
int indx = addConstant(pstate->state, getChunk(pstate), val); int indx = addConstant(pstate->state, getChunk(pstate), val);
if (indx > UINT16_MAX) { if (indx > UINT16_MAX) {
error(pstate, "UInt overflow! Too many constants in one chunk!"); error(pstate, "UInt overflow! Too many constants in one chunk!");
return 0;
} }
return (uint16_t)indx; return (uint16_t)indx;
@ -350,7 +339,6 @@ static void addLocal(CParseState *pstate, CToken name)
{ {
if (pstate->compiler->localCount > UINT8_MAX) { if (pstate->compiler->localCount > UINT8_MAX) {
error(pstate, "UInt overflow! Too many locals in scope!"); error(pstate, "UInt overflow! Too many locals in scope!");
return;
} }
Local *local = &pstate->compiler->locals[pstate->compiler->localCount++]; 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) { if (upvals > UINT8_MAX) {
error(pstate, "UInt overflow! Too many upvalues in scope!"); error(pstate, "UInt overflow! Too many upvalues in scope!");
return -1;
} }
// check and make sure we haven't already captured it // 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 tblType = 1; // array-like
} else { } else {
error(pstate, "Can't change table description type mid-definition!"); error(pstate, "Can't change table description type mid-definition!");
return;
} }
entries++; entries++;
@ -818,7 +804,7 @@ static void object(CParseState *pstate, bool canAssign, Precedence prec)
// "pop" the 1 value // "pop" the 1 value
valuePopped(pstate, 1); valuePopped(pstate, 1);
entries++; entries++;
} while (match(pstate, TOKEN_COMMA) && !pstate->hadError); } while (match(pstate, TOKEN_COMMA));
consume(pstate, TOKEN_RIGHT_BRACE, "Expected '}' to end object definition."); 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) static void defineVariable(CParseState *pstate, uint16_t global, bool forceLocal)
{ {
if (pstate->hadError)
return;
if (pstate->compiler->scopeDepth > 0 || forceLocal) { if (pstate->compiler->scopeDepth > 0 || forceLocal) {
markInitialized(pstate, global); markInitialized(pstate, global);
valuePopped(pstate, 1); // the local stays on the stack! valuePopped(pstate, 1); // the local stays on the stack!
@ -1177,7 +1160,7 @@ static void _proto(CParseState *pstate)
{ {
int entries = 0; 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)) { if (match(pstate, TOKEN_FUNC)) {
// define method // define method
consume(pstate, TOKEN_IDENTIFIER, "Expected identifier for 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) static void popLocals(CParseState *pstate, int toScope)
{ {
if (pstate->hadError)
return;
// count the locals in scope to pop // count the locals in scope to pop
int localsToPop = 0; int localsToPop = 0;
@ -1686,18 +1666,6 @@ static void continueStatement(CParseState *pstate)
writeJmpBack(pstate, pstate->compiler->loop.startBytecode); 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) static int expressionPrecedence(CParseState *pstate, int needed, Precedence prec, bool forceNeeded)
{ {
int lastExpected = pstate->compiler->expectedValues; int lastExpected = pstate->compiler->expectedValues;
@ -1787,10 +1755,6 @@ static void statement(CParseState *pstate)
static void declaration(CParseState *pstate) static void declaration(CParseState *pstate)
{ {
statement(pstate); statement(pstate);
// if we paniced, skip the whole statement!
if (pstate->panic)
synchronize(pstate);
} }
static CObjFunction *endCompiler(CParseState *pstate) static CObjFunction *endCompiler(CParseState *pstate)
@ -1812,7 +1776,6 @@ CObjFunction *cosmoP_compileString(CState *state, const char *source, const char
{ {
CParseState parser; CParseState parser;
CCompilerState compiler; CCompilerState compiler;
cosmoM_freezeGC(state); // ignore all GC events while compiling
initParseState(&parser, &compiler, state, source, module); initParseState(&parser, &compiler, state, source, module);
advance(&parser); advance(&parser);
@ -1825,24 +1788,10 @@ CObjFunction *cosmoP_compileString(CState *state, const char *source, const char
popLocals(&parser, 0); 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; CObjFunction *resFunc = compiler.function;
// finally free out parser states // finally free out parser states
endCompiler(&parser); endCompiler(&parser);
freeParseState(&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; return resFunc;
} }

View File

@ -7,6 +7,21 @@
#include <string.h> #include <string.h>
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() CState *cosmoV_newState()
{ {
// we use C's malloc because we don't want to trigger a GC with an invalid state // 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->panic = false;
state->freezeGC = 1; // we start frozen state->freezeGC = 1; // we start frozen
state->panic = NULL;
// GC // GC
state->objects = NULL; state->objects = NULL;

View File

@ -6,6 +6,8 @@
#include "ctable.h" #include "ctable.h"
#include "cvalue.h" #include "cvalue.h"
#include <setjmp.h>
struct CCallFrame struct CCallFrame
{ {
CObjClosure *closure; CObjClosure *closure;
@ -38,11 +40,17 @@ typedef struct ArrayCObj
int capacity; int capacity;
} ArrayCObj; } ArrayCObj;
typedef struct CPanic
{
jmp_buf jmp;
struct CPanic *prev;
} CPanic;
struct CState struct CState
{ {
bool panic;
int freezeGC; // when > 0, GC events will be ignored (for internal use) int freezeGC; // when > 0, GC events will be ignored (for internal use)
int frameCount; int frameCount;
CPanic *panic;
CObjError *error; // NULL, unless panic is true CObjError *error; // NULL, unless panic is true
CObj *objects; // tracks all of our allocated objects CObj *objects; // tracks all of our allocated objects
@ -64,10 +72,15 @@ struct CState
CValue stack[STACK_MAX]; // stack CValue stack[STACK_MAX]; // stack
}; };
CPanic *cosmoV_newPanic(CState *state);
void cosmoV_freePanic(CState *state);
COSMO_API CState *cosmoV_newState(); 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 // 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_register(CState *state, int pairs);
COSMO_API void cosmoV_freeState(CState *state);
COSMO_API void cosmoV_printStack(CState *state); COSMO_API void cosmoV_printStack(CState *state);
#endif #endif

View File

@ -10,6 +10,18 @@
#include <stdarg.h> #include <stdarg.h>
#include <string.h> #include <string.h>
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, ...) COSMO_API void cosmoV_pushFString(CState *state, const char *format, ...)
{ {
va_list args; va_list args;
@ -57,6 +69,7 @@ COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *
{ {
CObjFunction *func; CObjFunction *func;
if (cosmoV_protect(state)) {
if ((func = cosmoP_compileString(state, src, name)) != NULL) { if ((func = cosmoP_compileString(state, src, name)) != NULL) {
// success // success
#ifdef VM_DEBUG #ifdef VM_DEBUG
@ -68,10 +81,8 @@ COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *
*(cosmoV_getTop(state, 0)) = cosmoV_newRef(cosmoO_newClosure(state, func)); *(cosmoV_getTop(state, 0)) = cosmoV_newRef(cosmoO_newClosure(state, func));
return true; return true;
} }
}
// fail recovery
state->panic = false;
cosmoV_pushRef(state, (CObj *)state->error);
return false; 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 takes value on top of the stack and wraps an CObjError around it, then throws it
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..
*/ */
CObjError *cosmoV_throw(CState *state) void cosmoV_throw(CState *state)
{ {
StkPtr temp = cosmoV_getTop(state, 0); StkPtr temp = cosmoV_getTop(state, 0);
CObjError *error = cosmoO_newError(state, *temp); CObjError *error = cosmoO_newError(state, *temp);
state->error = error;
state->panic = true;
cosmoV_pop(state); // pops thrown value off the stack // replace the value on the stack with the error
return 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, ...) 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 // format the error string and push it onto the stack
va_list args; va_list args;
va_start(args, format); 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 // returns false if function call failed, true if function call succeeded
bool cosmoV_pcall(CState *state, int args, int nresults) bool cosmoV_pcall(CState *state, int args, int nresults)
{ {
StkPtr base = cosmoV_getTop(state, args); if (cosmoV_protect(state)) {
cosmoV_call(state, args, nresults);
if (!callCValue(state, *base, args, nresults, 0)) { return true;
} else {
printf("caught panic!\n");
// restore panic state // restore panic state
state->panic = false; state->panic = false;
@ -438,8 +447,6 @@ bool cosmoV_pcall(CState *state, int args, int nresults)
return false; return false;
} }
return true;
} }
// calls a callable object at stack->top - args - 1, passing the # of args to the callable, and // calls a callable object at stack->top - args - 1, passing the # of args to the callable, and

View File

@ -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_concat(CState *state, int vals);
COSMO_API void cosmoV_pushFString(CState *state, const char *format, ...); COSMO_API void cosmoV_pushFString(CState *state, const char *format, ...);
COSMO_API void cosmoV_printError(CState *state, CObjError *err); 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 cosmoV_error(CState *state, const char *format, ...);
COSMO_API void cosmo_insert(CState *state, int indx, CValue val); COSMO_API void cosmo_insert(CState *state, int indx, CValue val);