Minor table refactor, added cosmoV_compileString and loadstring() to baselib

cosmoV_compileString is recommended since it'll push the result (<error> or <closure>) onto the stack.

also, fixed some GC-related bugs, so yay!
This commit is contained in:
CPunch 2021-01-10 14:38:53 -06:00
parent 8dc8cef7dc
commit c510c9aebf
9 changed files with 122 additions and 59 deletions

View File

@ -57,6 +57,24 @@ int cosmoB_pcall(CState *state, int nargs, CValue *args) {
return 2;
}
int cosmoB_loadstring(CState *state, int nargs, CValue *args) {
if (nargs < 1) {
cosmoV_error(state, "loadstring() expected 1 argument, got %d!", nargs);
return 0;
}
if (!IS_STRING(args[0])) {
cosmoV_typeError(state, "loadstring()", "<string>", "%s", cosmoV_typeStr(args[0]));
return 0;
}
CObjString *str = cosmoV_readString(args[0]);
bool res = cosmoV_compileString(state, str->str, "");
cosmo_insert(state, 0, cosmoV_newBoolean(res));
return 2; // <boolean>, <closure> or <error>
}
// ================================================================ [STRING.*] ================================================================
// string.sub
@ -103,23 +121,32 @@ int cosmoB_sSub(CState *state, int nargs, CValue *args) {
}
void cosmoB_loadLibrary(CState *state) {
// print
cosmoV_pushString(state, "print");
cosmoV_pushCFunction(state, cosmoB_print);
const char *identifiers[] = {
"print",
"assert",
"type",
"pcall",
"loadstring"
};
// assert (for unit testing)
cosmoV_pushString(state, "assert");
cosmoV_pushCFunction(state, cosmoB_assert);
CosmoCFunction baseLib[] = {
cosmoB_print,
cosmoB_assert,
cosmoB_type,
cosmoB_pcall,
cosmoB_loadstring
};
// type
cosmoV_pushString(state, "type");
cosmoV_pushCFunction(state, cosmoB_type);
int i;
for (i = 0; i < sizeof(identifiers)/sizeof(identifiers[0]); i++) {
cosmoV_pushString(state, identifiers[i]);
cosmoV_pushCFunction(state, baseLib[i]);
}
// pcall
cosmoV_pushString(state, "pcall");
cosmoV_pushCFunction(state, cosmoB_pcall);
// register all the pushed c functions and the strings as globals
cosmoV_register(state, i);
// string.
// string.*
cosmoV_pushString(state, "string");
// sub
@ -127,19 +154,19 @@ void cosmoB_loadLibrary(CState *state) {
cosmoV_pushCFunction(state, cosmoB_sSub);
cosmoV_makeTable(state, 1);
// string.
// string.*
// register these all to the global table
cosmoV_register(state, 5);
// register "string" to that table
cosmoV_register(state, 1);
// make string object for CObjStrings
// sub
cosmoV_pushString(state, "sub");
cosmoV_pushCFunction(state, cosmoB_sSub);
cosmoV_makeObject(state, 1);
// grab the object from the stack and set the base protoObject
StkPtr obj = cosmoV_pop(state);
state->protoObjects[COBJ_STRING] = cosmoV_readObject(*obj);
}

View File

@ -55,7 +55,8 @@ void markTable(CState *state, CTable *tbl) {
if (tbl->table == NULL) // table is still being initialized
return;
for (int i = 0; i < tbl->capacity; i++) {
int cap = tbl->capacityMask + 1;
for (int i = 0; i < cap; i++) {
CTableEntry *entry = &tbl->table[i];
markValue(state, entry->key);
markValue(state, entry->val);
@ -67,7 +68,8 @@ void tableRemoveWhite(CState *state, CTable *tbl) {
if (tbl->table == NULL) // table is still being initialized
return;
for (int i = 0; i < tbl->capacity; i++) {
int cap = tbl->capacityMask + 1;
for (int i = 0; i < cap; i++) {
CTableEntry *entry = &tbl->table[i];
if (IS_OBJ(entry->key) && !(cosmoV_readObj(entry->key))->isMarked) { // if the key is a object and it's white (unmarked), remove it from the table
cosmoT_remove(state, tbl, entry->key);

View File

@ -1587,20 +1587,19 @@ CObjFunction* cosmoP_compileString(CState *state, const char *source, const char
endCompiler(&parser);
freeParseState(&parser);
// the VM still expects a result on the stack
cosmoV_pushValue(state, cosmoV_newNil());
cosmoM_unfreezeGC(state);
return NULL;
}
CObjFunction* resFunc = compiler.function;
// VM expects the closure on the stack :P (we do this before ending the compiler so our GC doesn't free it)
cosmoV_pushValue(state, cosmoV_newObj((CObj*)cosmoO_newClosure(state, resFunc)));
// 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_pushValue(state, cosmoV_newObj(resFunc));
cosmoM_unfreezeGC(state);
cosmoV_pop(state);
return resFunc;
}

View File

@ -33,14 +33,18 @@ CState *cosmoV_newState() {
state->openUpvalues = NULL;
state->error = NULL;
cosmoT_initTable(state, &state->strings, 8); // init string table
cosmoT_initTable(state, &state->globals, 8); // init global table
// set default proto objects
for (int i = 0; i < COBJ_MAX; i++)
state->protoObjects[i] = NULL;
// first, set all strings to NULL so our GC doesn't read garbage data
for (int i = 0; i < ISTRING_MAX; i++)
state->iStrings[i] = NULL;
cosmoT_initTable(state, &state->strings, 8); // init string table
cosmoT_initTable(state, &state->globals, 8); // init global table
// setup all strings used by the VM
state->iStrings[ISTRING_INIT] = cosmoO_copyString(state, "__init", 6);
state->iStrings[ISTRING_TOSTRING] = cosmoO_copyString(state, "__tostring", 10);
@ -61,10 +65,6 @@ CState *cosmoV_newState() {
// set the IString flags
for (int i = 0; i < ISTRING_MAX; i++)
state->iStrings[i]->isIString = true;
// set default proto objects
for (int i = 0; i < COBJ_MAX; i++)
state->protoObjects[i] = NULL;
return state;
}

View File

@ -24,21 +24,24 @@ unsigned int nextPow2(unsigned int x) {
}
void cosmoT_initTable(CState *state, CTable *tbl, int startCap) {
tbl->capacity = startCap != 0 ? startCap : ARRAY_START; // sanity check :P
startCap = startCap != 0 ? startCap : ARRAY_START; // sanity check :P
tbl->capacityMask = startCap - 1;
tbl->count = 0;
tbl->tombstones = 0;
tbl->table = NULL; // to let out GC know we're initalizing
tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * tbl->capacity);
tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * startCap);
// init everything to NIL
for (int i = 0; i < tbl->capacity; i++) {
for (int i = 0; i < startCap; i++) {
tbl->table[i].key = cosmoV_newNil();
tbl->table[i].val = cosmoV_newNil();
}
}
void cosmoT_addTable(CState *state, CTable *from, CTable *to) {
for (int i = 0; i < from->capacity; i++) {
int cap = from->capacityMask + 1;
for (int i = 0; i < cap; i++) {
CTableEntry *entry = &from->table[i];
if (!(IS_NIL(entry->key))) {
@ -49,7 +52,7 @@ void cosmoT_addTable(CState *state, CTable *from, CTable *to) {
}
void cosmoT_clearTable(CState *state, CTable *tbl) {
cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity);
cosmoM_freearray(state, CTableEntry, tbl->table, (tbl->capacityMask + 1));
}
uint32_t getObjectHash(CObj *obj) {
@ -57,7 +60,7 @@ uint32_t getObjectHash(CObj *obj) {
case COBJ_STRING:
return ((CObjString*)obj)->hash;
default:
return 0;
return (uint32_t)obj; // just "hash" the pointer
}
}
@ -115,13 +118,16 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin
size_t size = sizeof(CTableEntry) * newCapacity;
int cachedCount = tbl->count;
int newCount, oldCap;
cosmoM_checkGarbage(state, size); // if this allocation would cause a GC, run the GC
if (tbl->count < cachedCount) // the GC removed some objects from this table and resized it, ignore our resize event!
return;
CTableEntry *entries = cosmoM_xmalloc(state, size);
int newCount = 0;
oldCap = tbl->capacityMask + 1;
newCount = 0;
// set all nodes as NIL : NIL
for (int i = 0; i < newCapacity; i++) {
@ -130,7 +136,7 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin
}
// move over old values to the new buffer
for (int i = 0; i < tbl->capacity; i++) {
for (int i = 0; i < oldCap; i++) {
CTableEntry *oldEntry = &tbl->table[i];
if (IS_NIL(oldEntry->key))
continue; // skip empty keys
@ -143,10 +149,10 @@ static void resizeTbl(CState *state, CTable *tbl, int newCapacity, bool canShrin
}
// free the old table
cosmoM_freearray(state, CTableEntry, tbl->table, tbl->capacity);
cosmoM_freearray(state, CTableEntry, tbl->table, oldCap);
tbl->table = entries;
tbl->capacity = newCapacity;
tbl->capacityMask = newCapacity - 1;
tbl->count = newCount;
tbl->tombstones = 0;
}
@ -164,14 +170,15 @@ bool cosmoT_checkShrink(CState *state, CTable *tbl) {
// returns a pointer to the allocated value
COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) {
// make sure we have enough space allocated
if (tbl->count + 1 > (int)(tbl->capacity * MAX_TABLE_FILL)) {
int cap = tbl->capacityMask + 1;
if (tbl->count + 1 > (int)(cap * MAX_TABLE_FILL)) {
// grow table
int newCap = tbl->capacity * GROW_FACTOR;
int newCap = cap * GROW_FACTOR;
resizeTbl(state, tbl, newCap, true);
}
// insert into the table
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key); // -1 for our capacity mask
CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key); // -1 for our capacity mask
if (IS_NIL(entry->key)) {
if (IS_NIL(entry->val)) // is it empty?
@ -191,7 +198,7 @@ bool cosmoT_get(CTable *tbl, CValue key, CValue *val) {
return false;
}
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key);
CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key);
*val = entry->val;
// return if get was successful
@ -201,7 +208,7 @@ bool cosmoT_get(CTable *tbl, CValue key, CValue *val) {
bool cosmoT_remove(CState* state, CTable *tbl, CValue key) {
if (tbl->count == 0) return 0; // sanity check
CTableEntry *entry = findEntry(tbl->table, tbl->capacity - 1, key);
CTableEntry *entry = findEntry(tbl->table, tbl->capacityMask, key);
if (IS_NIL(entry->key)) // sanity check
return false;
@ -220,7 +227,7 @@ COSMO_API int cosmoT_count(CTable *tbl) {
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash) {
if (tbl->count == 0) return 0; // sanity check
uint32_t indx = hash & (tbl->capacity - 1); // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation
uint32_t indx = hash & tbl->capacityMask; // since we know the capacity will *always* be a power of 2, we can use bitwise & to perform a MUCH faster mod operation
// keep looking for an open slot in the entries array
while (true) {
@ -234,14 +241,15 @@ CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32
return (CObjString*)cosmoV_readObj(entry->key);
}
indx = (indx + 1) & (tbl->capacity - 1); // fast mod here too
indx = (indx + 1) & tbl->capacityMask; // fast mod here too
}
}
// for debugging purposes
void cosmoT_printTable(CTable *tbl, const char *name) {
printf("==== [[%s]] ====\n", name);
for (int i = 0; i < tbl->capacity; i++) {
int cap = tbl->capacityMask + 1;
for (int i = 0; i < cap; i++) {
CTableEntry *entry = &tbl->table[i];
if (!(IS_NIL(entry->key))) {
printValue(entry->key);

View File

@ -13,20 +13,19 @@ typedef struct CTableEntry {
typedef struct CTable {
int count;
int capacity;
int capacityMask; // +1 to get the capacity
int tombstones;
CTableEntry *table;
} CTable;
COSMO_API void cosmoT_initTable(CState *state, CTable *tbl, int startCap);
COSMO_API void cosmoT_clearTable(CState *state, CTable *tbl);
COSMO_API void cosmoT_addTable(CState *state, CTable *from, CTable *to);
COSMO_API int cosmoT_count(CTable *tbl);
bool cosmoT_checkShrink(CState *state, CTable *tbl);
CObjString *cosmoT_lookupString(CTable *tbl, const char *str, int length, uint32_t hash);
COSMO_API CValue *cosmoT_insert(CState *state, CTable *tbl, CValue key);
CValue *cosmoT_insert(CState *state, CTable *tbl, CValue key);
bool cosmoT_get(CTable *tbl, CValue key, CValue *val);
bool cosmoT_remove(CState *state, CTable *tbl, CValue key);

View File

@ -2,6 +2,7 @@
#include "cstate.h"
#include "cdebug.h"
#include "cmem.h"
#include "cparse.h"
#include <stdarg.h>
#include <string.h>
@ -27,7 +28,26 @@ COSMO_API void cosmo_insert(CState *state, int indx, CValue val) {
state->top++;
}
void cosmoV_printError(CState *state, CObjError *err) {
COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *name) {
CObjFunction *func;
if ((func = cosmoP_compileString(state, src, name)) != NULL) {
// success
disasmChunk(&func->chunk, func->module->str, 0);
// push function onto the stack so it doesn't it cleaned up by the GC, at the same stack location put our closure
cosmoV_pushValue(state, cosmoV_newObj(func));
*(cosmoV_getTop(state, 0)) = cosmoV_newObj(cosmoO_newClosure(state, func));
return true;
}
// fail
state->panic = false;
cosmoV_pushValue(state, cosmoV_newObj(state->error));
return false;
}
COSMO_API void cosmoV_printError(CState *state, CObjError *err) {
// print stack trace
for (int i = 0; i < err->frameCount; i++) {
CCallFrame *frame = &err->frames[i];
@ -473,10 +493,11 @@ int _tbl__next(CState *state, int nargs, CValue *args) {
CObjTable *table = (CObjTable*)cosmoV_readObj(val);
// while the entry is invalid, go to the next entry
int cap = table->tbl.capacityMask + 1;
CTableEntry *entry;
do {
entry = &table->tbl.table[index++];
} while (IS_NIL(entry->key) && index < table->tbl.capacity);
} while (IS_NIL(entry->key) && index < cap);
cosmoO_setUserI(state, obj, index); // update the userdata
if (!IS_NIL(entry->key)) { // if the entry is valid, return it's key and value pair

View File

@ -24,6 +24,15 @@ 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);
/*
compiles string into a <closure>, if successful, <closure> will be pushed onto the stack otherwise the <error> will be pushed.
returns:
false : <error> is at the top of the stack
true : <closure> is at the top of the stack
*/
COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *name);
COSMO_API bool cosmoV_get(CState *state, CObj *obj, CValue key, CValue *val);
COSMO_API bool cosmoV_set(CState *state, CObj *obj, CValue key, CValue val);

View File

@ -32,16 +32,14 @@ int cosmoB_input(CState *state, int nargs, CValue *args) {
}
static void interpret(CState *state, const char *script, const char *mod) {
// cosmoP_compileString pushes the result onto the stack (NIL or COBJ_CLOSURE)
CObjFunction* func = cosmoP_compileString(state, script, mod);
if (func != NULL) {
disasmChunk(&func->chunk, func->name != NULL ? func->name->str : "_main", 0);
// cosmoV_compileString pushes the result onto the stack (COBJ_ERROR or COBJ_CLOSURE)
if (cosmoV_compileString(state, script, mod)) {
COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected
if (res == COSMOVM_RUNTIME_ERR)
cosmoV_printError(state, state->error);
} else {
cosmoV_pop(state); // pop the error off the stack
cosmoV_printError(state, state->error);
}