Added CObjError, cosmoV_throw(), pcall(), and cosmoV_printError()

Errors are now handled very differently, parser errors and VM errors are now treated the same.
When cosmoV_error is called, cosmoV_throw is also called, which formats the error object and sets the panic state.
state->error now points to the latest CObjError when state->panic is true. To get a nice formatted Objection message, use
cosmoV_printError() and pass the state->error. pcall() was added to the standard base library. When called, the first argument
passed is called with the subsequent arguments given. If the call completed successfully, `true`,`nil` is returned. However
when an error occurs during the call, `false`,`<error>` is returned. Simply print the `<error>` to retrieve the error string.
This commit is contained in:
CPunch 2021-01-05 22:27:59 -06:00
parent 417a1f15f1
commit eb2f50e456
13 changed files with 195 additions and 65 deletions

View File

@ -21,7 +21,7 @@ int cosmoB_assert(CState *state, int nargs, CValue *args) {
}
if (!IS_BOOLEAN(args[0])) {
cosmoV_error(state, "assert() expected (<boolean>), got (%s!)", cosmoV_typeStr(args[0]));
cosmoV_typeError(state, "assert()", "<boolean>", "%s", cosmoV_typeStr(args[0]));
return 0;
}
@ -43,13 +43,28 @@ int cosmoB_type(CState *state, int nargs, CValue *args) {
return 1; // 1 return value, the type string :D
}
int cosmoB_pcall(CState *state, int nargs, CValue *args) {
if (nargs < 1) {
cosmoV_error(state, "pcall() expected at least 1 argument!");
return 0;
}
// call the passed callable
COSMOVMRESULT res = cosmoV_pcall(state, nargs-1, 1);
// insert false before the result
cosmo_insert(state, 0, cosmoV_newBoolean(res == COSMOVM_OK));
cosmoV_printStack(state);
return 2;
}
// ================================================================ [STRING.*] ================================================================
// string.sub
int cosmoB_sSub(CState *state, int nargs, CValue *args) {
if (nargs == 2) {
if (!IS_STRING(args[0]) || !IS_NUMBER(args[1])) {
cosmoV_error(state, "string.sub() expected (<string>, <number>), got (%s, %s)!", cosmoV_typeStr(args[0]), cosmoV_typeStr(args[1]));
cosmoV_typeError(state, "string.sub()", "<string>, <number>", "%s, %s", cosmoV_typeStr(args[0]), cosmoV_typeStr(args[1]));
return 0;
}
@ -58,14 +73,14 @@ int cosmoB_sSub(CState *state, int nargs, CValue *args) {
// make sure we stay within memory
if (indx < 0 || indx >= str->length) {
cosmoV_error(state, "string.sub() Expected index to be 0-%d, got %d!", str->length, indx);
cosmoV_error(state, "string.sub() expected index to be 0-%d, got %d!", str->length, indx);
return 0;
}
cosmoV_pushLString(state, str->str + ((int)indx), str->length - ((int)indx));
} else if (nargs == 3) {
if (!IS_STRING(args[0]) || !IS_NUMBER(args[1]) || !IS_NUMBER(args[2])) {
cosmoV_error(state, "string.sub() expected (<string>, <number>, <number>), got (%s, %s, %s)!", cosmoV_typeStr(args[0]), cosmoV_typeStr(args[1]), cosmoV_typeStr(args[2]));
cosmoV_typeError(state, "string.sub()", "<string>, <number>, <number>", "%s, %s, %s", cosmoV_typeStr(args[0]), cosmoV_typeStr(args[1]), cosmoV_typeStr(args[2]));
return 0;
}
@ -75,7 +90,7 @@ int cosmoB_sSub(CState *state, int nargs, CValue *args) {
// make sure we stay within memory
if (indx + length < 0 || indx + length >= str->length || indx < 0 || indx >= str->length) {
cosmoV_error(state, "string.sub() Expected subbed string goes out of bounds, max length is %d!", str->length);
cosmoV_error(state, "string.sub() expected subbed string goes out of bounds, max length is %d!", str->length);
return 0;
}
@ -101,6 +116,10 @@ void cosmoB_loadLibrary(CState *state) {
cosmoV_pushString(state, "type");
cosmoV_pushCFunction(state, cosmoB_type);
// pcall
cosmoV_pushString(state, "pcall");
cosmoV_pushCFunction(state, cosmoB_pcall);
// string.
cosmoV_pushString(state, "string");
@ -112,7 +131,7 @@ void cosmoB_loadLibrary(CState *state) {
// string.
// register these all to the global table
cosmoV_register(state, 4);
cosmoV_register(state, 5);
}
// ================================================================ [DEBUG] ================================================================

View File

@ -8,5 +8,9 @@ COSMO_API void cosmoB_loadDebug(CState *state);
COSMO_API int cosmoB_print(CState *state, int nargs, CValue *args);
COSMO_API int cosmoB_assert(CState *state, int nargs, CValue *args);
COSMO_API int cosmoB_type(CState *state, int nargs, CValue *args);
COSMO_API int cosmoB_pcall(CState *state, int nargs, CValue *args);
#define cosmoV_typeError(state, name, expectedTypes, formatStr, ...) \
cosmoV_error(state, name " expected (" expectedTypes "), got (" formatStr ")!", __VA_ARGS__);
#endif

View File

@ -205,6 +205,7 @@ CToken parseString(CLexState *state) {
case 'r': case 'n': appendBuffer(state, '\n'); break;
case 't': appendBuffer(state, '\t'); break;
case '\\': appendBuffer(state, '\\'); break;
case '"': appendBuffer(state, '"'); break;
default: {
if (isNumerical(peek(state))) {
char *numStart = state->currentChar;

View File

@ -120,6 +120,16 @@ void blackenObject(CState *state, CObj *obj) {
markObject(state, (CObj*)method->obj);
break;
}
case COBJ_ERROR: {
CObjError *err = (CObjError*)obj;
markValue(state, err->err);
// mark callframes
for (int i = 0; i < err->frameCount; i++)
markObject(state, (CObj*)err->frames[i].closure);
break;
}
case COBJ_CLOSURE: {
CObjClosure *closure = (CObjClosure*)obj;
markObject(state, (CObj*)closure->function);
@ -232,8 +242,10 @@ void markRoots(CState *state) {
// mark the user defined roots
markUserRoots(state);
// mark our proto object
// mark other misc. internally reserved objects
markObject(state, (CObj*)state->protoObj);
markObject(state, (CObj*)state->error);
traceGrays(state);
}

View File

@ -3,6 +3,7 @@
#include "cobj.h"
#include "cmem.h"
#include "cvm.h"
#include "clex.h"
#include <string.h>
@ -75,6 +76,12 @@ void cosmoO_free(CState *state, CObj* obj) {
cosmoM_free(state, CObjMethod, obj); // we don't own the closure or the object so /shrug
break;
}
case COBJ_ERROR: {
CObjError *err = (CObjError*)obj;
cosmoM_freearray(state, CCallFrame, err->frames, err->frameCount);
cosmoM_free(state, CObjError, obj);
break;
}
case COBJ_CLOSURE: {
CObjClosure* closure = (CObjClosure*)obj;
cosmoM_freearray(state, CObjUpval*, closure->upvalues, closure->upvalueCount);
@ -142,6 +149,21 @@ CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func) {
return cfunc;
}
CObjError *cosmoO_newError(CState *state, CValue err) {
CObjError *cerror = (CObjError*)cosmoO_allocateBase(state, sizeof(CObjError), COBJ_ERROR);
cerror->err = err;
cerror->frameCount = state->frameCount;
// allocate the callframe
cerror->frames = cosmoM_xmalloc(state, sizeof(CCallFrame) * cerror->frameCount);
// clone the call frame
for (int i = 0; i < state->frameCount; i++)
cerror->frames[i] = state->callFrame[i];
return cerror;
}
CObjMethod *cosmoO_newCMethod(CState *state, CObjCFunction *func, CObjObject *obj) {
CObjMethod *method = (CObjMethod*)cosmoO_allocateBase(state, sizeof(CObjMethod), COBJ_METHOD);
method->func = cosmoV_newObj(func);
@ -251,6 +273,11 @@ CObjString *cosmoO_pushVFString(CState *state, const char *format, va_list args)
cosmoV_pushString(state, va_arg(args, char *));
break;
}
case 't': { // CToken *
CToken *token = va_arg(args, CToken *);
cosmoV_pushLString(state, token->start, token->length);
break;
}
default: {
char temp[2];
temp[0] = '%';
@ -420,6 +447,10 @@ CObjString *cosmoO_toString(CState *state, CObj *obj) {
return cosmoO_copyString(state, buf, sz);
}
}
case COBJ_ERROR: {
CObjError *err = (CObjError*)obj;
return cosmoV_toString(state, err->err);
}
case COBJ_DICT: {
char buf[64];
int sz = sprintf(buf, "<dict> %p", (void*)obj) + 1; // +1 for the null character

View File

@ -8,6 +8,7 @@
#include "ctable.h"
typedef struct CState CState;
typedef struct CCallFrame CCallFrame;
typedef uint32_t cosmo_Flag;
typedef enum {
@ -16,6 +17,7 @@ typedef enum {
COBJ_DICT, // dictionary
COBJ_FUNCTION,
COBJ_CFUNCTION,
COBJ_ERROR,
// internal use
COBJ_METHOD,
COBJ_CLOSURE,
@ -43,6 +45,15 @@ typedef struct CObjString {
uint32_t hash; // for hashtable lookup
} CObjString;
typedef struct CObjError {
CommonHeader; // "is a" CObj
bool parserError; // if true, cosmoV_printError will format the error to the lexer
int frameCount;
int line; // reserved for parser errors
CValue err; // error string
CCallFrame *frames;
} CObjError;
typedef struct CObjObject {
CommonHeader; // "is a" CObj
cosmo_Flag istringFlags; // enables us to have a much faster lookup for reserved IStrings (like __init, __index, etc.)
@ -129,6 +140,7 @@ CObjObject *cosmoO_newObject(CState *state);
CObjDict *cosmoO_newDictionary(CState *state);
CObjFunction *cosmoO_newFunction(CState *state);
CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func);
CObjError *cosmoO_newError(CState *state, CValue err);
CObjMethod *cosmoO_newMethod(CState *state, CObjClosure *func, CObjObject *obj);
CObjMethod *cosmoO_newCMethod(CState *state, CObjCFunction *func, CObjObject *obj);
CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func);
@ -161,6 +173,7 @@ CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uin
'%d' - decimal numbers [int]
'%f' - floating point [double]
'%s' - strings [const char*]
'%t' - cosmo tokens [CToken *]
*/
CObjString *cosmoO_pushVFString(CState *state, const char *format, va_list args);

View File

@ -36,6 +36,7 @@ typedef struct CObjUpval CObjUpval;
typedef struct CObjFunction CObjFunction;
typedef struct CObjCFunction CObjCFunction;
typedef struct CObjMethod CObjMethod;
typedef struct CObjError CObjError;
typedef struct CObjObject CObjObject;
typedef struct CObjClosure CObjClosure;

View File

@ -140,29 +140,41 @@ static void freeParseState(CParseState *pstate) {
cosmoL_freeLexState(pstate->state, pstate->lex);
}
static void errorAt(CParseState *pstate, CToken *token, const char * msg) {
static void errorAt(CParseState *pstate, CToken *token, const char *format, va_list args) {
if (pstate->hadError)
return;
fprintf(stderr, "[line %d] Objection", token->line);
if (token->type == TOKEN_EOF) {
fprintf(stderr, " at end");
cosmoV_pushString(pstate->state, "At end: ");
} else if (!(token->type == TOKEN_ERROR)) {
fprintf(stderr, " at '%.*s'", token->length, token->start);
cosmoV_pushFString(pstate->state, "At '%t'", token); // this is why the '%t' exist in cosmoO_pushFString lol
}
printf(": \n\t%s\n", msg);
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;
}
static void errorAtCurrent(CParseState *pstate, const char *msg) {
errorAt(pstate, &pstate->current, msg);
static void errorAtCurrent(CParseState *pstate, const char *format, ...) {
va_list args;
va_start(args, format);
errorAt(pstate, &pstate->current, format, args);
va_end(args);
}
static void error(CParseState *pstate, const char *msg) {
errorAt(pstate, &pstate->previous, msg);
static void error(CParseState *pstate, const char *format, ...) {
va_list args;
va_start(args, format);
errorAt(pstate, &pstate->previous, format, args);
va_end(args);
}
static void advance(CParseState *pstate) {

View File

@ -31,7 +31,9 @@ CState *cosmoV_newState() {
state->top = state->stack;
state->frameCount = 0;
state->openUpvalues = NULL;
state->protoObj = NULL;
state->error = NULL;
cosmoT_initTable(state, &state->strings, 8); // init string table
cosmoT_initTable(state, &state->globals, 8); // init global table

View File

@ -37,6 +37,7 @@ typedef struct CState {
int frameCount;
CObjObject *protoObj; // start met obj for all objects (NULL by default)
CObjError *error; // NULL, unless panic is true
CObj *objects; // tracks all of our allocated objects
CObj *userRoots; // user definable roots, this holds CObjs that should be considered "roots", lets the VM know you are holding a reference to a CObj in your code
ArrayCObj grayStack; // keeps track of which objects *haven't yet* been traversed in our GC, but *have been* found

122
src/cvm.c
View File

@ -15,43 +15,78 @@ COSMO_API void cosmoV_pushFString(CState *state, const char *format, ...) {
va_end(args);
}
void cosmoV_error(CState *state, const char *format, ...) {
if (state->panic)
return;
state->panic = true;
// inserts val at state->top - indx - 1, moving everything else up
COSMO_API void cosmo_insert(CState *state, int indx, CValue val) {
StkPtr tmp = cosmoV_getTop(state, indx);
// moves everything up
for (StkPtr i = state->top; i > tmp; i--)
*i = *(i-1);
*tmp = val;
state->top++;
}
void cosmoV_printError(CState *state, CObjError *err) {
// print stack trace
for (int i = 0; i < state->frameCount; i++) {
CCallFrame *frame = &state->callFrame[i];
for (int i = 0; i < err->frameCount; i++) {
CCallFrame *frame = &err->frames[i];
CObjFunction *function = frame->closure->function;
CChunk *chunk = &function->chunk;
int line = chunk->lineInfo[frame->pc - chunk->buf - 1];
if (i == state->frameCount - 1) { // it's the last call frame, prepare for the objection to be printed
if (i == err->frameCount - 1 && !err->parserError) // it's the last call frame (and not a parser error), prepare for the objection to be printed
fprintf(stderr, "Objection in %.*s on [line %d] in ", function->module->length, function->module->str, line);
if (function->name == NULL) { // unnamed chunk
fprintf(stderr, "%s\n\t", UNNAMEDCHUNK);
} else {
fprintf(stderr, "%.*s()\n\t", function->name->length, function->name->str);
}
} else {
else
fprintf(stderr, "[line %d] in ", line);
if (function->name == NULL) { // unnamed chunk
fprintf(stderr, "%s\n", UNNAMEDCHUNK);
} else {
fprintf(stderr, "%.*s()\n", function->name->length, function->name->str);
}
if (function->name == NULL) { // unnamed chunk
fprintf(stderr, "%s\n", UNNAMEDCHUNK);
} else {
fprintf(stderr, "%.*s()\n", function->name->length, function->name->str);
}
}
if (err->parserError)
fprintf(stderr, "Objection while parsing on [line %d]\n", err->line);
// finally, print the error message
CObjString *errString = cosmoV_toString(state, err->err);
printf("\t%.*s\n", errString->length, errString->str);
}
/*
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..
*/
CObjError* 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;
}
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);
CObjString *errString = cosmoO_pushVFString(state, format, args);
cosmoO_pushVFString(state, format, args);
va_end(args);
printf("%.*s\n", errString->length, errString->str);
//cosmoV_printStack(state);
// throw the error onto the state
cosmoV_throw(state);
}
CObjUpval *captureUpvalue(CState *state, CValue *local) {
@ -144,7 +179,7 @@ bool invokeMethod(CState* state, CObjObject *obj, CValue func, int args, int nre
calls a native C Function with # args on the stack, nresults are pushed onto the stack upon return.
returns:
false: state paniced during C Function, stack is preserved so that the error is left on the stack
false: state paniced during C Function, error is at state->error
true: state->top is moved to base + offset + nresults, with nresults pushed onto the stack from base + offset
*/
static bool callCFunction(CState *state, CosmoCFunction cfunc, int args, int nresults, int offset) {
@ -155,9 +190,6 @@ static bool callCFunction(CState *state, CosmoCFunction cfunc, int args, int nre
int nres = cfunc(state, args, savedBase + 1);
cosmoM_unfreezeGC(state);
// if the state paniced during the c function, return false and leave the stack preserved (so the error is left on the stack)
if (state->panic)
return false;
// caller function wasn't expecting this many return values, cap it
if (nres > nresults)
@ -168,6 +200,10 @@ static bool callCFunction(CState *state, CosmoCFunction cfunc, int args, int nre
state->top = savedBase + offset; // set stack
// if the state paniced during the c function, return false
if (state->panic)
return false;
// push the return value back onto the stack
memmove(state->top, results, sizeof(CValue) * nres); // copies the return values to the top of the stack
state->top += nres; // and make sure to move state->top to match
@ -183,7 +219,7 @@ static bool callCFunction(CState *state, CosmoCFunction cfunc, int args, int nre
calls a raw closure object with # args on the stack, nresults are pushed onto the stack upon return.
returns:
false: state paniced, stack is preserved so that the error is left on the stack
false: state paniced, error is at state->error
true: stack->top is moved to base + offset + nresults, with nresults pushed onto the stack from base + offset
*/
static bool rawCall(CState *state, CObjClosure *closure, int args, int nresults, int offset) {
@ -215,8 +251,6 @@ static bool rawCall(CState *state, CObjClosure *closure, int args, int nresults,
// execute
int nres = cosmoV_execute(state);
if (nres == -1) // panic state
return false;
if (nres > nresults) // caller function wasn't expecting this many return values, cap it
nres = nresults;
@ -227,6 +261,9 @@ static bool rawCall(CState *state, CObjClosure *closure, int args, int nresults,
// pop the callframe and return results :)
popCallFrame(state, offset);
if (state->panic) // panic state
return false;
// push the return values back onto the stack
memmove(state->top, results, sizeof(CValue) * nres); // copies the return values to the top of the stack
state->top += nres; // and make sure to move state->top to match
@ -247,6 +284,8 @@ bool callCValue(CState *state, CValue func, int args, int nresults, int offset)
switch (cosmoV_readObj(func)->type) {
case COBJ_CLOSURE:
return rawCall(state, cosmoV_readClosure(func), args, nresults, offset);
case COBJ_CFUNCTION:
return callCFunction(state, cosmoV_readCFunction(func), args, nresults, offset);
case COBJ_METHOD: {
CObjMethod *method = (CObjMethod*)cosmoV_readObj(func);
return invokeMethod(state, method->obj, method->func, args, nresults, offset + 1);
@ -277,8 +316,6 @@ bool callCValue(CState *state, CValue func, int args, int nresults, int offset)
}
break;
}
case COBJ_CFUNCTION:
return callCFunction(state, cosmoV_readCFunction(func), args, nresults, offset);
default:
cosmoV_error(state, "Cannot call non-function value %s!", cosmoV_typeStr(func));
return false;
@ -295,31 +332,20 @@ bool invokeMethod(CState* state, CObjObject *obj, CValue func, int args, int nre
return callCValue(state, func, args+1, nresults, offset);
}
// wraps cosmoV_call in a protected state, error string will be pushed onto the stack if function call failed, else return values are passed
// wraps cosmoV_call in a protected state, CObjError will be pushed onto the stack if function call failed, else return values are passed
COSMOVMRESULT cosmoV_pcall(CState *state, int args, int nresults) {
StkPtr base = cosmoV_getTop(state, args);
COSMOVMRESULT res = cosmoV_call(state, args, nresults);
if (res != COSMOVM_OK) {
if (!callCValue(state, *base, args, nresults, 0)) {
// restore panic state
state->panic = false;
state->error = NULL;
// error is on the stack, grab it
StkPtr err = cosmoV_getTop(state, 0);
state->top = base;
if (nresults > 0) {
cosmoV_pushValue(state, *err);
// push the nils to fill up the expected return values
for (int i = 0; i < nresults - 1; i++) { // -1 since the we already pushed the important value
cosmoV_pushValue(state, cosmoV_newNil());
}
}
cosmoV_pushValue(state, cosmoV_newObj(state->error));
return COSMOVM_RUNTIME_ERR;
}
return res;
return COSMOVM_OK;
}
/*
@ -327,7 +353,7 @@ COSMOVMRESULT cosmoV_pcall(CState *state, int args, int nresults) {
returns:
COSMOVM_OK: callable object exited normally
COSMOVM_RUNTIME_ERR: an error occurred, the error will be at the top of the stack (the rest of the stack is junk)
COSMOVM_RUNTIME_ERR: an error occurred, grab the error from state->error
*/
COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) {
StkPtr val = cosmoV_getTop(state, args); // function will always be right above the args

View File

@ -14,12 +14,16 @@ typedef enum {
// args = # of pass parameters, nresults = # of expected results
COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults);
COSMO_API COSMOVMRESULT cosmoV_pcall(CState *state, int args, int nresults);
COSMO_API void cosmoV_makeObject(CState *state, int pairs);
COSMO_API void cosmoV_makeDictionary(CState *state, int pairs);
COSMO_API bool cosmoV_getObject(CState *state, CObjObject *object, CValue key, CValue *val);
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_error(CState *state, const char *format, ...);
COSMO_API void cosmo_insert(CState *state, int indx, CValue val);
// nice to have wrappers

View File

@ -40,8 +40,12 @@ static void interpret(CState *state, const char *script, const char *mod) {
COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected
if (res == COSMOVM_RUNTIME_ERR)
state->panic = false; // so our repl isn't broken
cosmoV_printError(state, state->error);
} else {
cosmoV_printError(state, state->error);
}
state->panic = false; // so our repl isn't broken
}
static void repl() {