diff --git a/Makefile b/Makefile index 3dceea8..1742be2 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # make clean && make && ./bin/cosmo CC=clang -CFLAGS=-fPIE -O3 #-g3 -LDFLAGS=#-fsanitize=address +CFLAGS=-fPIE -g3 #-O3 +LDFLAGS=-fsanitize=address OUT=bin/cosmo CHDR=\ diff --git a/src/cdebug.c b/src/cdebug.c index 4d511c6..09bf092 100644 --- a/src/cdebug.c +++ b/src/cdebug.c @@ -116,6 +116,12 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { } case OP_CLOSE: return simpleInstruction("OP_CLOSE", offset); + case OP_NEWOBJECT: + return shortOperandInstruction("OP_NEWOBJECT", chunk, offset); + case OP_GETOBJECT: + return simpleInstruction("OP_GETOBJECT", offset); + case OP_SETOBJECT: + return simpleInstruction("OP_SETOBJECT", offset); case OP_ADD: return simpleInstruction("OP_ADD", offset); case OP_SUB: diff --git a/src/cmem.c b/src/cmem.c index 9831621..062fead 100644 --- a/src/cmem.c +++ b/src/cmem.c @@ -80,7 +80,7 @@ void markArray(CState *state, CValueArray *array) { void blackenObject(CState *state, CObj *obj) { switch (obj->type) { case COBJ_STRING: - case COBJ_TABLE: // TODO: when metatables are added, make sure they're marked + case COBJ_OBJECT: // TODO: when metatables are added, make sure they're marked case COBJ_CFUNCTION: // stubbed break; diff --git a/src/cobj.c b/src/cobj.c index 959d199..e59f334 100644 --- a/src/cobj.c +++ b/src/cobj.c @@ -42,10 +42,10 @@ void cosmoO_freeObject(CState *state, CObj* obj) { cosmoM_free(state, CObjString, objStr); break; } - case COBJ_TABLE: { - CObjTable *objTbl = (CObjTable*)obj; + case COBJ_OBJECT: { + CObjObject *objTbl = (CObjObject*)obj; cosmoT_clearTable(state, &objTbl->tbl); - cosmoM_free(state, CObjTable, objTbl); + cosmoM_free(state, CObjObject, objTbl); break; } case COBJ_UPVALUE: { @@ -88,10 +88,10 @@ bool cosmoO_equalObject(CObj* obj1, CObj* obj2) { } } -CObjTable *cosmoO_newTable(CState *state) { - CObjTable *tbl = (CObjTable*)cosmoO_allocateObject(state, sizeof(CObjTable), COBJ_TABLE); +CObjObject *cosmoO_newObject(CState *state, int startCap) { + CObjObject *tbl = (CObjObject*)cosmoO_allocateObject(state, sizeof(CObjObject), COBJ_OBJECT); - cosmoT_initTable(state, &tbl->tbl, 8); // start the table at 8 + cosmoT_initTable(state, &tbl->tbl, startCap); return tbl; } @@ -188,9 +188,9 @@ CObjString *cosmoO_toString(CState *state, CObj *val) { CObjFunction *func = (CObjFunction*)val; return func->name != NULL ? func->name : cosmoO_copyString(state, UNNAMEDCHUNK, strlen(UNNAMEDCHUNK)); } - case COBJ_TABLE: { // TODO: maybe not safe?? + case COBJ_OBJECT: { // TODO: maybe not safe?? char buf[64]; - int sz = sprintf(buf, " %p", val) + 1; // +1 for the null character + int sz = sprintf(buf, " %p", val) + 1; // +1 for the null character return cosmoO_copyString(state, buf, sz); } default: @@ -205,8 +205,8 @@ void printObject(CObj *o) { printf("\"%.*s\"", objStr->length, objStr->str); break; } - case COBJ_TABLE: { - printf(" %p", o); + case COBJ_OBJECT: { + printf(" %p", o); return; } case COBJ_UPVALUE: { diff --git a/src/cobj.h b/src/cobj.h index 59150b2..2948483 100644 --- a/src/cobj.h +++ b/src/cobj.h @@ -10,7 +10,7 @@ typedef struct CState CState; typedef enum { COBJ_STRING, - COBJ_TABLE, + COBJ_OBJECT, COBJ_FUNCTION, COBJ_CFUNCTION, // internal use @@ -35,11 +35,11 @@ typedef struct CObjString { uint32_t hash; // for hashtable lookup } CObjString; -typedef struct CObjTable { +typedef struct CObjObject { CommonHeader; // "is a" CObj CTable tbl; - //struct CObjTable *meta; // metatable, used to describe table behavior -} CObjTable; + //struct CObjObject *meta; // metaobject, used to describe object behavior +} CObjObject; typedef struct CObjFunction { CommonHeader; // "is a" CObj @@ -69,13 +69,13 @@ typedef struct CObjUpval { } CObjUpval; #define IS_STRING(x) isObjType(x, COBJ_STRING) -#define IS_TABLE(x) isObjType(x, COBJ_TABLE) +#define IS_TABLE(x) isObjType(x, COBJ_OBJECT) #define IS_FUNCTION(x) isObjType(x, COBJ_FUNCTION) #define IS_CFUNCTION(x) isObjType(x, COBJ_CFUNCTION) #define IS_CLOSURE(x) isObjType(x, COBJ_CLOSURE) #define cosmoV_readString(x) ((CObjString*)cosmoV_readObj(x)) -#define cosmoV_readTable(x) ((CObjTable*)cosmoV_readObj(x)) +#define cosmoV_readObject(x) ((CObjObject*)cosmoV_readObj(x)) #define cosmoV_readFunction(x) ((CObjFunction*)cosmoV_readObj(x)) #define cosmoV_readCFunction(x) (((CObjCFunction*)cosmoV_readObj(x))->cfunc) #define cosmoV_readClosure(x) ((CObjClosure*)cosmoV_readObj(x)) @@ -89,7 +89,7 @@ void cosmoO_freeObject(CState *state, CObj* obj); bool cosmoO_equalObject(CObj* obj1, CObj* obj2); -CObjTable *cosmoO_newTable(CState *state); +CObjObject *cosmoO_newObject(CState *state, int startCap); CObjFunction *cosmoO_newFunction(CState *state); CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func); CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func); diff --git a/src/coperators.h b/src/coperators.h index 4661f1f..4a1aeb3 100644 --- a/src/coperators.h +++ b/src/coperators.h @@ -29,6 +29,9 @@ typedef enum { OP_CALL, // calls top[-uint8_t] OP_CLOSURE, OP_CLOSE, + OP_NEWOBJECT, + OP_GETOBJECT, + OP_SETOBJECT, // ARITHMETIC OP_ADD, diff --git a/src/cparse.c b/src/cparse.c index ea850e6..bb76fd3 100644 --- a/src/cparse.c +++ b/src/cparse.c @@ -30,6 +30,7 @@ typedef enum { PREC_TERM, // + - PREC_FACTOR, // * / PREC_UNARY, // ! - + PREC_OBJ, // {} PREC_CALL, // . () PREC_PRIMARY // everything else } Precedence; @@ -502,13 +503,41 @@ static void call_(CParseState *pstate, bool canAssign) { } } +static void object(CParseState *pstate, bool canAssign) { + // already consumed the beginning '{' + int entries = 0; + + consume(pstate, TOKEN_RIGHT_BRACE, "Expected '}' to end object definition!"); + + writeu8(pstate, OP_NEWOBJECT); + writeu8(pstate, entries); + valuePushed(pstate, 1); +} + +static void dot(CParseState *pstate, bool canAssign) { + consume(pstate, TOKEN_IDENTIFIER, "Expect property name after '.'."); + uint8_t name = identifierConstant(pstate, &pstate->previous); + writeu8(pstate, OP_LOADCONST); + writeu16(pstate, name); + valuePushed(pstate, 1); + + if (canAssign && match(pstate, TOKEN_EQUAL)) { + expression(pstate); + writeu8(pstate, OP_SETOBJECT); + valuePopped(pstate, 3); // pops key, value & object + } else { + writeu8(pstate, OP_GETOBJECT); + valuePopped(pstate, 1); // pops key & object but also pushes the field so total popped is 1 + } +} + ParseRule ruleTable[] = { [TOKEN_LEFT_PAREN] = {group, call_, PREC_CALL}, [TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE}, - [TOKEN_LEFT_BRACE] = {NULL, NULL, PREC_NONE}, + [TOKEN_LEFT_BRACE] = {object, NULL, PREC_OBJ}, [TOKEN_RIGHT_BRACE] = {NULL, NULL, PREC_NONE}, [TOKEN_COMMA] = {NULL, NULL, PREC_NONE}, - [TOKEN_DOT] = {NULL, NULL, PREC_NONE}, + [TOKEN_DOT] = {NULL, dot, PREC_CALL}, [TOKEN_DOT_DOT] = {NULL, concat, PREC_CONCAT}, [TOKEN_MINUS] = {unary, binary, PREC_TERM}, [TOKEN_PLUS] = {NULL, binary, PREC_TERM}, diff --git a/src/ctable.c b/src/ctable.c index db0eed0..b88c67b 100644 --- a/src/ctable.c +++ b/src/ctable.c @@ -8,13 +8,13 @@ #define MAX_TABLE_FILL 0.75 void cosmoT_initTable(CState *state, CTable *tbl, int startCap) { - tbl->capacity = startCap; + tbl->capacity = startCap != 0 ? startCap : ARRAY_START; // sanity check :P tbl->count = 0; tbl->table = NULL; // to let out GC know we're initalizing - tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * startCap); + tbl->table = cosmoM_xmalloc(state, sizeof(CTableEntry) * tbl->capacity); // init everything to NIL - for (int i = 0; i < startCap; i++) { + for (int i = 0; i < tbl->capacity; i++) { tbl->table[i].key = cosmoV_newNil(); tbl->table[i].val = cosmoV_newNil(); } diff --git a/src/cvm.c b/src/cvm.c index 600a664..0f82094 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -317,6 +317,63 @@ int cosmoV_execute(CState *state) { cosmoV_pop(state); break; } + case OP_NEWOBJECT: { + uint8_t entries = READBYTE(); + StkPtr key, val; + CObjObject *newObj = cosmoO_newObject(state, entries * 3); // start the table with enough space to hopefully prevent reallocation since that's costly + cosmoV_pushValue(state, cosmoV_newObj(newObj)); // so our GC doesn't free our new object + + for (int i = 0; i < entries; i++) { + val = cosmoV_getTop(state, (i*2) + 2); + key = cosmoV_getTop(state, (i*2) + 1); + + // set key/value pair + CValue *newVal = cosmoT_insert(state, &newObj->tbl, *key); + *newVal = *val; + } + + // once done, pop everything off the stack + push new object + cosmoV_setTop(state, (entries * 2) + 1); + cosmoV_pushValue(state, cosmoV_newObj(newObj)); + break; + } + case OP_GETOBJECT: { + StkPtr key = cosmoV_getTop(state, 0); // key should be the top of the stack + StkPtr temp = cosmoV_getTop(state, 1); // after that should be the object + + // sanity check + if (!(temp->type == COSMO_TOBJ) || !(temp->val.obj->type == COBJ_OBJECT)) { + runtimeError(state, "Couldn't get from non-object!"); + break; + } + + CObjObject *object = (CObjObject*)temp->val.obj; + CValue val; // to hold our value + + cosmoT_get(&object->tbl, *key, &val); + cosmoV_setTop(state, 2); // pops the object & the key + cosmoV_pushValue(state, val); // pushes the field result + break; + } + case OP_SETOBJECT: { + StkPtr value = cosmoV_getTop(state, 0); // value is at the top of the stack + StkPtr key = cosmoV_getTop(state, 1); + StkPtr temp = cosmoV_getTop(state, 2); // object is after the key + + // sanity check + if (!(temp->type == COSMO_TOBJ) || !(temp->val.obj->type == COBJ_OBJECT)) { + runtimeError(state, "Couldn't set a field on a non-object!"); + break; + } + + CObjObject *object = (CObjObject*)temp->val.obj; + CValue *newVal = cosmoT_insert(state, &object->tbl, *key); + *newVal = *value; + + // pop everything off the stack + cosmoV_setTop(state, 3); + break; + } case OP_ADD: { // pop 2 values off the stack & try to add them together BINARYOP(cosmoV_newNumber, +); break;