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
parent 1d2ba217af
commit f26376e6f5
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?
}
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)

View File

@ -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);

View File

@ -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;
}

View File

@ -7,6 +7,21 @@
#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()
{
// 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;

View File

@ -6,6 +6,8 @@
#include "ctable.h"
#include "cvalue.h"
#include <setjmp.h>
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

View File

@ -10,6 +10,18 @@
#include <stdarg.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, ...)
{
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

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_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);