#include "cobj.h" #include "clex.h" #include "cmem.h" #include "cstate.h" #include "ctable.h" #include "cvm.h" #include #include // we don't actually hash the whole string :eyes: uint32_t hashString(const char *str, size_t sz) { uint32_t hash = sz; size_t step = (sz >> 5) + 1; for (size_t i = sz; i >= step; i -= step) { hash = ((hash << 5) + (hash >> 2)) + str[i - 1]; } return hash; } CObj *cosmoO_allocateBase(CState *state, size_t sz, CObjType type) { CObj *obj = (CObj *)cosmoM_xmalloc(state, sz); obj->type = type; obj->isMarked = false; obj->proto = state->protoObjects[type]; obj->next = state->objects; state->objects = obj; #ifdef GC_DEBUG printf("allocated %s %p\n", cosmoO_typeStr(obj), obj); #endif return obj; } void cosmoO_free(CState *state, CObj *obj) { #ifdef GC_DEBUG printf("freeing %s %p\n", cosmoO_typeStr(obj), obj); #endif switch (obj->type) { case COBJ_STRING: { CObjString *objStr = (CObjString *)obj; cosmoM_freeArray(state, char, objStr->str, objStr->length + 1); cosmoM_free(state, CObjString, objStr); break; } case COBJ_OBJECT: { CObjObject *objObj = (CObjObject *)obj; cosmoT_clearTable(state, &objObj->tbl); cosmoM_free(state, CObjObject, objObj); break; } case COBJ_TABLE: { CObjTable *tbl = (CObjTable *)obj; cosmoT_clearTable(state, &tbl->tbl); cosmoM_free(state, CObjTable, tbl); break; } case COBJ_UPVALUE: { cosmoM_free(state, CObjUpval, obj); break; } case COBJ_FUNCTION: { CObjFunction *objFunc = (CObjFunction *)obj; cleanChunk(state, &objFunc->chunk); cosmoM_free(state, CObjFunction, objFunc); break; } case COBJ_CFUNCTION: { cosmoM_free(state, CObjCFunction, obj); break; } case COBJ_METHOD: { 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); cosmoM_free(state, CObjClosure, closure); break; } case COBJ_MAX: default: { /* stubbed, should never happen */ } } } bool cosmoO_equal(CState *state, CObj *obj1, CObj *obj2) { CObjObject *proto1, *proto2; CValue eq1, eq2; if (obj1 == obj2) // its the same reference, this compares strings for us since they're interned // anyways :) return true; // its not the same type, maybe both 's have the same '__equal' metamethod in their protos? if (obj1->type != obj2->type) goto _eqFail; switch (obj1->type) { case COBJ_STRING: { /* we already compared the pointers at the top of the function, this prevents the `__equal` metamethod from being checked. If you plan on using `__equal` with strings just remove this case! */ return false; } case COBJ_CFUNCTION: { CObjCFunction *cfunc1 = (CObjCFunction *)obj1; CObjCFunction *cfunc2 = (CObjCFunction *)obj2; if (cfunc1->cfunc == cfunc2->cfunc) return true; goto _eqFail; } case COBJ_METHOD: { CObjMethod *method1 = (CObjMethod *)obj1; CObjMethod *method2 = (CObjMethod *)obj2; if (cosmoV_equal(state, method1->func, method2->func)) return true; goto _eqFail; } case COBJ_CLOSURE: { CObjClosure *closure1 = (CObjClosure *)obj1; CObjClosure *closure2 = (CObjClosure *)obj2; // we just compare the function pointer if (closure1->function == closure2->function) return true; goto _eqFail; } default: goto _eqFail; } _eqFail: // this is pretty expensive (bad lookup caching helps a lot), but it only all gets run if both // objects have protos & both have the `__equal` metamethod defined so... it should stay light // for the majority of cases if ((proto1 = cosmoO_grabProto(obj1)) != NULL && (proto2 = cosmoO_grabProto(obj2)) != NULL && // make sure both protos exist cosmoO_getIString( state, proto1, ISTRING_EQUAL, &eq1) && // grab the `__equal` metamethod from the first proto, if fail abort cosmoO_getIString( state, proto2, ISTRING_EQUAL, &eq2) && // grab the `__equal` metamethod from the second proto, if fail abort cosmoV_equal(state, eq1, eq2)) { // compare the two `__equal` metamethods // now finally, call the `__equal` metamethod (, ) cosmoV_pushValue(state, eq1); cosmoV_pushRef(state, obj1); cosmoV_pushRef(state, obj2); cosmoV_call(state, 2, 1); // check return value and make sure it's a boolean if (!IS_BOOLEAN(*cosmoV_getTop(state, 0))) { cosmoV_error(state, "__equal expected to return , got %s!", cosmoV_typeStr(*cosmoV_pop(state))); } // return the result return cosmoV_readBoolean(*cosmoV_pop(state)); } return false; } CObjObject *cosmoO_newObject(CState *state) { CObjObject *obj = (CObjObject *)cosmoO_allocateBase(state, sizeof(CObjObject), COBJ_OBJECT); obj->istringFlags = 0; obj->userP = NULL; // reserved for C API obj->userT = 0; obj->isLocked = false; cosmoV_pushRef(state, (CObj *)obj); // so our GC can keep track of it cosmoT_initTable(state, &obj->tbl, ARRAY_START); cosmoV_pop(state); return obj; } CObjTable *cosmoO_newTable(CState *state) { CObjTable *obj = (CObjTable *)cosmoO_allocateBase(state, sizeof(CObjTable), COBJ_TABLE); // init the table (might cause a GC event) cosmoV_pushRef(state, (CObj *)obj); // so our GC can keep track of obj cosmoT_initTable(state, &obj->tbl, ARRAY_START); cosmoV_pop(state); return obj; } CObjFunction *cosmoO_newFunction(CState *state) { CObjFunction *func = (CObjFunction *)cosmoO_allocateBase(state, sizeof(CObjFunction), COBJ_FUNCTION); func->args = 0; func->upvals = 0; func->variadic = false; func->name = NULL; func->module = NULL; initChunk(state, &func->chunk, ARRAY_START); return func; } CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func) { CObjCFunction *cfunc = (CObjCFunction *)cosmoO_allocateBase(state, sizeof(CObjCFunction), COBJ_CFUNCTION); cfunc->cfunc = func; return cfunc; } CObjError *cosmoO_newError(CState *state, CValue err) { CCallFrame *frames = cosmoM_xmalloc(state, sizeof(CCallFrame) * state->frameCount); CObjError *cerror = (CObjError *)cosmoO_allocateBase(state, sizeof(CObjError), COBJ_ERROR); cerror->err = err; cerror->frameCount = state->frameCount; cerror->frames = frames; cerror->parserError = false; // clone the call frame for (int i = 0; i < state->frameCount; i++) { cerror->frames[i] = state->callFrame[i]; } return cerror; } CObjMethod *cosmoO_newMethod(CState *state, CValue func, CObj *obj) { CObjMethod *method = (CObjMethod *)cosmoO_allocateBase(state, sizeof(CObjMethod), COBJ_METHOD); method->func = func; method->obj = obj; return method; } CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func) { // initialize array of pointers CObjUpval **upvalues = cosmoM_xmalloc(state, sizeof(CObjUpval *) * func->upvals); for (int i = 0; i < func->upvals; i++) { upvalues[i] = NULL; } CObjClosure *closure = (CObjClosure *)cosmoO_allocateBase(state, sizeof(CObjClosure), COBJ_CLOSURE); closure->function = func; closure->upvalues = upvalues; closure->upvalueCount = func->upvals; return closure; } CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val) { CObjUpval *upval = (CObjUpval *)cosmoO_allocateBase(state, sizeof(CObjUpval), COBJ_UPVALUE); upval->val = val; upval->closed = cosmoV_newNil(); upval->next = NULL; return upval; } CObjString *cosmoO_copyString(CState *state, const char *str, size_t length) { uint32_t hash = hashString(str, length); CObjString *lookup = cosmoT_lookupString(&state->strings, str, length, hash); // have we already interned this string? if (lookup != NULL) return lookup; char *buf = cosmoM_xmalloc(state, sizeof(char) * (length + 1)); // +1 for null terminator memcpy(buf, str, length); // copy string to heap buf[length] = '\0'; // don't forget our null terminator return cosmoO_allocateString(state, buf, length, hash); } // length shouldn't include the null terminator! str should be a null terminated string! (char array // should also have been allocated using cosmoM_xmalloc!) CObjString *cosmoO_takeString(CState *state, char *str, size_t length) { uint32_t hash = hashString(str, length); CObjString *lookup = cosmoT_lookupString(&state->strings, str, length, hash); // have we already interned this string? if (lookup != NULL) { cosmoM_freeArray(state, char, str, length + 1); // free our passed character array, it's unneeded! return lookup; } return cosmoO_allocateString(state, str, length, hash); } CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uint32_t hash) { CObjString *strObj = (CObjString *)cosmoO_allocateBase(state, sizeof(CObjString), COBJ_STRING); strObj->isIString = false; strObj->str = (char *)str; strObj->length = sz; strObj->hash = hash; // push/pop to make sure GC doesn't collect it cosmoV_pushRef(state, (CObj *)strObj); cosmoT_insert(state, &state->strings, cosmoV_newRef((CObj *)strObj)); cosmoV_pop(state); return strObj; } CObjString *cosmoO_pushVFString(CState *state, const char *format, va_list args) { StkPtr start = state->top; const char *end; char c; int len; while (true) { end = strchr(format, '%'); // grab the next occurrence of '%' len = -1; // -1 means no length specified if (end == NULL) // the end, no '%' found break; // push the string before '%' cosmoV_pushLString(state, format, (end - format)); reentry: c = end[1]; // the character right after '%' switch (c) { case 'd': // int cosmoV_pushNumber(state, va_arg(args, int)); break; case 'f': // double cosmoV_pushNumber(state, va_arg(args, double)); break; case 's': // char * if (len >= 0) // the length is specified cosmoV_pushLString(state, va_arg(args, char *), len); else cosmoV_pushString(state, va_arg(args, char *)); break; case '*': // length specifier len = va_arg(args, int); end++; // skip '*' goto reentry; default: { char temp[2]; temp[0] = '%'; temp[1] = c; cosmoV_pushLString(state, temp, 2); } } format = end + 2; // + 2 because of % and the following character } cosmoV_pushString(state, format); // push the rest of the string cosmoV_concat(state, state->top - start); // use cosmoV_concat to concat all the strings on the stack return cosmoV_readString(*start); // start should be state->top - 1 } // walks the protos of obj and checks for proto bool cosmoO_isDescendant(CObj *obj, CObjObject *proto) { CObjObject *curr = obj->proto; while (curr != NULL) { if (curr == proto) return true; // found proto! return true curr = ((CObj *)curr)->proto; } // we didn't find the proto return false; } // returns false if error thrown void cosmoO_getRawObject(CState *state, CObjObject *proto, CValue key, CValue *val, CObj *obj) { if (!cosmoT_get(state, &proto->tbl, key, val)) { // if the field doesn't exist in the object, check the proto if (cosmoO_getIString(state, proto, ISTRING_GETTER, val) && IS_TABLE(*val) && cosmoT_get(state, &cosmoV_readTable(*val)->tbl, key, val)) { cosmoV_pushValue(state, *val); // push function cosmoV_pushRef(state, (CObj *)obj); // push object cosmoV_call(state, 1, 1); // call the function with the 1 argument *val = *cosmoV_pop(state); // set value to the return value of __index return; } // maybe the field is defined in the proto? if (proto->_obj.proto != NULL) { cosmoO_getRawObject(state, proto->_obj.proto, key, val, obj); return; } *val = cosmoV_newNil(); } } void cosmoO_setRawObject(CState *state, CObjObject *proto, CValue key, CValue val, CObj *obj) { CValue ret; // if the object is locked, throw an error if (proto->isLocked) { cosmoV_error(state, "Couldn't set on a locked object!"); return; } // check for __setters if (cosmoO_getIString(state, proto, ISTRING_SETTER, &ret) && IS_TABLE(ret) && cosmoT_get(state, &cosmoV_readTable(ret)->tbl, key, &ret)) { cosmoV_pushValue(state, ret); // push function cosmoV_pushRef(state, (CObj *)obj); // push object cosmoV_pushValue(state, val); // push new value cosmoV_call(state, 2, 0); return; } // if the key is an IString, we need to reset the cache if (IS_STRING(key) && cosmoV_readString(key)->isIString) proto->istringFlags = 0; // reset cache if (IS_NIL(val)) { // if we're setting an index to nil, we can safely mark that as a tombstone cosmoT_remove(state, &proto->tbl, key); } else { CValue *newVal = cosmoT_insert(state, &proto->tbl, key); *newVal = val; } } void cosmoO_setUserP(CObjObject *object, void *p) { object->userP = p; } void *cosmoO_getUserP(CObjObject *object) { return object->userP; } void cosmoO_setUserI(CObjObject *object, int i) { object->userI = i; } int cosmoO_getUserI(CObjObject *object) { return object->userI; } void cosmoO_setUserT(CObjObject *object, int t) { object->userT = t; } int cosmoO_getUserT(CObjObject *object) { return object->userT; } void cosmoO_lock(CObjObject *object) { object->isLocked = true; } void cosmoO_unlock(CObjObject *object) { object->isLocked = false; } bool rawgetIString(CState *state, CObjObject *object, int flag, CValue *val) { if (readFlag(object->istringFlags, flag)) return false; // it's been cached as bad if (!cosmoT_get(state, &object->tbl, cosmoV_newRef(state->iStrings[flag]), val)) { // mark it bad! setFlagOn(object->istringFlags, flag); return false; } return true; // :) } bool cosmoO_getIString(CState *state, CObjObject *object, int flag, CValue *val) { CObjObject *obj = object; do { if (rawgetIString(state, obj, flag, val)) return true; } while ((obj = obj->_obj.proto) != NULL); // sets obj to it's proto and compares it to NULL return false; // obj->proto was false, the istring doesn't exist in this object chain } void cosmoO_indexObject(CState *state, CObjObject *object, CValue key, CValue *val) { if (cosmoO_getIString(state, object, ISTRING_INDEX, val)) { cosmoV_pushValue(state, *val); // push function cosmoV_pushRef(state, (CObj *)object); // push object cosmoV_pushValue(state, key); // push key cosmoV_call(state, 2, 1); // call the function with the 2 arguments *val = *cosmoV_pop(state); // set value to the return value of __index } else { // there's no __index function defined! cosmoV_error(state, "Couldn't index object without __index function!"); } } void cosmoO_newIndexObject(CState *state, CObjObject *object, CValue key, CValue val) { CValue ret; // return value for cosmoO_getIString if (cosmoO_getIString(state, object, ISTRING_NEWINDEX, &ret)) { cosmoV_pushValue(state, ret); // push function cosmoV_pushRef(state, (CObj *)object); // push object cosmoV_pushValue(state, key); // push key & value pair cosmoV_pushValue(state, val); cosmoV_call(state, 3, 0); } else { // there's no __newindex function defined cosmoV_error(state, "Couldn't set index on object without __newindex function!"); } } CObjString *cosmoO_toString(CState *state, CObj *obj) { CObjObject *protoObject = cosmoO_grabProto(obj); CValue res; // use user-defined __tostring if (protoObject != NULL && cosmoO_getIString(state, protoObject, ISTRING_TOSTRING, &res)) { cosmoV_pushValue(state, res); cosmoV_pushRef(state, (CObj *)obj); cosmoV_call(state, 1, 1); // make sure the __tostring function returned a string StkPtr ret = cosmoV_getTop(state, 0); if (!IS_STRING(*ret)) { cosmoV_error(state, "__tostring expected to return , got %s!", cosmoV_typeStr(*ret)); return cosmoO_copyString(state, "", 5); } // return string cosmoV_pop(state); return (CObjString *)cosmoV_readRef(*ret); } switch (obj->type) { case COBJ_STRING: { return (CObjString *)obj; } case COBJ_CLOSURE: { // should be transparent to the user imo CObjClosure *closure = (CObjClosure *)obj; return cosmoO_toString(state, (CObj *)closure->function); } case COBJ_FUNCTION: { CObjFunction *func = (CObjFunction *)obj; return func->name != NULL ? func->name : cosmoO_copyString(state, UNNAMEDCHUNK, strlen(UNNAMEDCHUNK)); } case COBJ_CFUNCTION: { CObjCFunction *cfunc = (CObjCFunction *)obj; char buf[64]; int sz = sprintf(buf, " %p", (void *)cfunc->cfunc) + 1; // +1 for the null character return cosmoO_copyString(state, buf, sz); } case COBJ_OBJECT: { char buf[64]; int sz = sprintf(buf, " %p", (void *)obj) + 1; // +1 for the null character return cosmoO_copyString(state, buf, sz); } case COBJ_ERROR: { CObjError *err = (CObjError *)obj; return cosmoV_toString(state, err->err); } case COBJ_TABLE: { char buf[64]; int sz = sprintf(buf, " %p", (void *)obj) + 1; // +1 for the null character return cosmoO_copyString(state, buf, sz); } default: { char buf[64]; int sz = sprintf(buf, " %p", (void *)obj) + 1; // +1 for the null character return cosmoO_copyString(state, buf, sz); } } } cosmo_Number cosmoO_toNumber(CState *state, CObj *obj) { CObjObject *proto = cosmoO_grabProto(obj); CValue res; if (proto != NULL && cosmoO_getIString(state, proto, ISTRING_TONUMBER, &res)) { cosmoV_pushValue(state, res); cosmoV_pushRef(state, (CObj *)obj); cosmoV_call(state, 1, 1); // call res, expect 1 return val of StkPtr temp = cosmoV_getTop(state, 0); if (!IS_NUMBER(*temp)) { cosmoV_error(state, "__tonumber expected to return , got %s!", cosmoV_typeStr(*temp)); } // return number cosmoV_pop(state); return cosmoV_readNumber(*temp); } switch (obj->type) { case COBJ_STRING: { CObjString *str = (CObjString *)obj; return strtod(str->str, NULL); } default: // maybe in the future throw an error? return 0; } } int cosmoO_count(CState *state, CObj *obj) { CObjObject *proto = cosmoO_grabProto(obj); CValue res; if (proto != NULL && cosmoO_getIString(state, proto, ISTRING_COUNT, &res)) { cosmoV_pushValue(state, res); cosmoV_pushRef(state, (CObj *)obj); cosmoV_call(state, 1, 1); // call res, we expect 1 return value of type StkPtr ret = cosmoV_getTop(state, 0); if (!IS_NUMBER(*ret)) { cosmoV_error(state, "__count expected to return , got %s!", cosmoV_typeStr(*ret)); return 0; } // return number cosmoV_pop(state); return (int)cosmoV_readNumber(*ret); } switch (obj->type) { case COBJ_TABLE: { // returns the # of entries in the hash table CObjTable *tbl = (CObjTable *)obj; return cosmoT_count(&tbl->tbl); } case COBJ_STRING: { // returns the length of the string CObjString *str = (CObjString *)obj; return str->length; } default: cosmoV_error(state, "Couldn't get # (count) of %s!", cosmoO_typeStr(obj)); return 0; } } void printObject(CObj *o) { printf("%s ", cosmoO_typeStr(o)); switch (o->type) { case COBJ_STRING: { CObjString *objStr = (CObjString *)o; printf("\"%.*s\"", objStr->length, objStr->str); break; } case COBJ_OBJECT: { printf("%p", (void *)o); break; } case COBJ_TABLE: { CObjTable *tbl = (CObjTable *)o; printf("%p", (void *)tbl); break; } case COBJ_FUNCTION: { CObjFunction *objFunc = (CObjFunction *)o; if (objFunc->name != NULL) printf("%.*s", objFunc->name->length, objFunc->name->str); else printf("%s", UNNAMEDCHUNK); break; } case COBJ_CFUNCTION: { CObjCFunction *objCFunc = (CObjCFunction *)o; printf("%p", (void *)objCFunc->cfunc); break; } case COBJ_ERROR: { CObjError *err = (CObjError *)o; printf("%p -> ", (void *)o); cosmoV_printValue(err->err); break; } case COBJ_METHOD: { CObjMethod *method = (CObjMethod *)o; printf("%p -> ", (void *)method); cosmoV_printValue(method->func); break; } case COBJ_CLOSURE: { CObjClosure *closure = (CObjClosure *)o; printf("%p -> ", (void *)closure); printObject((CObj *)closure->function); // just print the function break; } case COBJ_UPVALUE: { CObjUpval *upval = (CObjUpval *)o; printf("%p -> ", (void *)upval->val); cosmoV_printValue(*upval->val); break; } default: printf("%p", (void *)o); } } const char *cosmoO_typeStr(CObj *obj) { switch (obj->type) { case COBJ_STRING: return ""; case COBJ_OBJECT: return ""; case COBJ_TABLE: return ""; case COBJ_FUNCTION: return ""; case COBJ_CFUNCTION: return ""; case COBJ_ERROR: return ""; case COBJ_METHOD: return ""; case COBJ_CLOSURE: return ""; case COBJ_UPVALUE: return ""; default: return ""; // TODO: maybe panic? could be a malformed object :eyes: } }