From c42a72dfad84edb30740d51b2b6cf07cdd05942b Mon Sep 17 00:00:00 2001 From: CPunch Date: Mon, 9 Nov 2020 19:44:12 -0600 Subject: [PATCH] Major refactoring, added classes, many bug fixes --- .vscode/settings.json | 3 +- src/cbaselib.c | 5 +- src/cbaselib.h | 2 +- src/cdebug.c | 12 +-- src/clex.c | 34 +++------ src/clex.h | 3 +- src/cmem.c | 11 ++- src/cmem.h | 2 +- src/cobj.c | 53 +++++++++---- src/cobj.h | 20 +++-- src/coperators.h | 1 + src/cosmo.h | 2 + src/cparse.c | 173 +++++++++++++++++++++++++++++++----------- src/cparse.h | 29 ------- src/cstate.c | 8 ++ src/cstate.h | 4 +- src/cvalue.c | 7 +- src/cvm.c | 136 +++++++++++++++++++++++---------- src/cvm.h | 2 +- src/main.c | 13 ++-- test.cosmo | 27 ++++++- 21 files changed, 353 insertions(+), 194 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ff69fd..45155d7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "tuple": "cpp", "type_traits": "cpp", "utility": "cpp", - "vector": "cpp" + "vector": "cpp", + "typeinfo": "c" } } \ No newline at end of file diff --git a/src/cbaselib.c b/src/cbaselib.c index c02b592..b3d1cf4 100644 --- a/src/cbaselib.c +++ b/src/cbaselib.c @@ -9,13 +9,12 @@ void cosmoB_loadlibrary(CState *state) { cosmoM_unfreezeGC(state); } -int cosmoB_print(CState *state, int nargs, CValue *args) { +CValue cosmoB_print(CState *state, int nargs, CValue *args) { for (int i = 0; i < nargs; i++) { CObjString *str = cosmoV_toString(state, args[i]); printf("%s", cosmoO_readCString(str)); } - printf("\n"); - return 0; // print doesn't return any args + return cosmoV_newNil(); // print doesn't return any args } \ No newline at end of file diff --git a/src/cbaselib.h b/src/cbaselib.h index 34f3136..b3aa377 100644 --- a/src/cbaselib.h +++ b/src/cbaselib.h @@ -4,6 +4,6 @@ #include "cstate.h" COSMO_API void cosmoB_loadlibrary(CState *state); -COSMO_API int cosmoB_print(CState *state, int nargs, CValue *args); +COSMO_API CValue cosmoB_print(CState *state, int nargs, CValue *args); #endif \ No newline at end of file diff --git a/src/cdebug.c b/src/cdebug.c index 09bf092..2a903fe 100644 --- a/src/cdebug.c +++ b/src/cdebug.c @@ -32,14 +32,6 @@ int constInstruction(const char *name, CChunk *chunk, int offset, int indent) { return offset + 1 + (sizeof(uint16_t) / sizeof(INSTRUCTION)); // consume opcode + uint } -int ABOperandInstruction(const char *name, CChunk *chunk, int offset) { - int args = readu8Chunk(chunk, offset + 1); - int nresults = readu8Chunk(chunk, offset + 2); - - printf("%-16s [%03d] [%03d]", name, args, nresults); - return offset + 3; -} - // public methods in the cdebug.h header void disasmChunk(CChunk *chunk, const char *name, int indent) { @@ -91,7 +83,7 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { case OP_POP: return shortOperandInstruction("OP_POP", chunk, offset); case OP_CALL: - return ABOperandInstruction("OP_CALL", chunk, offset); + return shortOperandInstruction("OP_CALL", chunk, offset); case OP_CLOSURE: { int index = readu16Chunk(chunk, offset + 1); printf("%-16s [%05d] - ", "OP_CLOSURE", index); @@ -153,7 +145,7 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { case OP_CONCAT: return shortOperandInstruction("OP_CONCAT", chunk, offset); case OP_RETURN: - return shortOperandInstruction("OP_RETURN", chunk, offset); + return simpleInstruction("OP_RETURN", offset); default: printf("Unknown opcode! [%d]\n", i); exit(0); diff --git a/src/clex.c b/src/clex.c index f2e1300..f7bbb16 100644 --- a/src/clex.c +++ b/src/clex.c @@ -12,6 +12,7 @@ CReservedWord reservedWords[] = { {TOKEN_FALSE, "false", 5}, {TOKEN_FOR, "for", 3}, {TOKEN_FUNCTION, "function", 8}, + {TOKEN_CLASS, "class", 5}, {TOKEN_IF, "if", 2}, {TOKEN_LOCAL, "local", 5}, {TOKEN_NIL, "nil", 3}, @@ -21,11 +22,9 @@ CReservedWord reservedWords[] = { {TOKEN_THEN, "then", 4}, {TOKEN_TRUE, "true", 4}, {TOKEN_VAR, "var", 3}, - {TOKEN_THIS, "this", 4}, {TOKEN_WHILE, "while", 5} }; - static CToken makeToken(CLexState *state, CTokenType type) { CToken token; token.type = type; @@ -49,7 +48,7 @@ static CToken makeError(CLexState *state, const char *msg) { } static inline bool isEnd(CLexState *state) { - return state->isEnd; + return *state->currentChar == '\0'; } static inline bool isNumerical(char c) { @@ -103,14 +102,13 @@ void skipWhitespace(CLexState *state) { while (true) { char c = peek(state); switch (c) { + case '\n': // mark new line + state->line++; case ' ': case '\r': case '\t': next(state); // consume the whitespace break; - case '\n': // mark new line, make the main loop consume it - state->line++; - return; case '-': // consume comments if (peekNext(state) == '-') { @@ -171,8 +169,6 @@ CLexState *cosmoL_newLexState(CState *cstate, const char *source) { state->currentChar = (char*)source; state->line = 1; state->lastLine = 0; - state->openedBraces = 0; - state->isEnd = false; state->lastType = TOKEN_ERROR; return state; @@ -195,16 +191,12 @@ _scanTokenEnter: switch (c) { // single character tokens - case '(': state->openedBraces++; return makeToken(state, TOKEN_LEFT_PAREN); - case ')': state->openedBraces--; return makeToken(state, TOKEN_RIGHT_PAREN); - case '{': state->openedBraces++; return makeToken(state, TOKEN_LEFT_BRACE); - case '}': state->openedBraces--; return makeToken(state, TOKEN_RIGHT_BRACE); - case '[': state->openedBraces++; return makeToken(state, TOKEN_LEFT_BRACKET); - case ']': state->openedBraces--; return makeToken(state, TOKEN_RIGHT_BRACKET); - case '\0': - state->isEnd = true; - if (state->lastType == TOKEN_EOS) - return makeToken(state, TOKEN_EOF); + case '(': return makeToken(state, TOKEN_LEFT_PAREN); + case ')': return makeToken(state, TOKEN_RIGHT_PAREN); + case '{': return makeToken(state, TOKEN_LEFT_BRACE); + case '}': return makeToken(state, TOKEN_RIGHT_BRACE); + case '[': return makeToken(state, TOKEN_LEFT_BRACKET); + case ']': return makeToken(state, TOKEN_RIGHT_BRACKET); // fall through case ';': return makeToken(state, TOKEN_EOS); case ',': return makeToken(state, TOKEN_COMMA); @@ -212,12 +204,6 @@ _scanTokenEnter: case '-': return makeToken(state, TOKEN_MINUS); case '*': return makeToken(state, TOKEN_STAR); case '/': return makeToken(state, TOKEN_SLASH); - case '\n': { // might be treated like a TOKEN_EOS - if (state->openedBraces == 0 && state->lastType != TOKEN_EOS) - return makeToken(state, TOKEN_EOS); - else // go back to the start - goto _scanTokenEnter; - } // two character tokens case '.': return match(state, '.') ? makeToken(state, TOKEN_DOT_DOT) : makeToken(state, TOKEN_DOT); diff --git a/src/clex.h b/src/clex.h index 12616d5..9963645 100644 --- a/src/clex.h +++ b/src/clex.h @@ -46,6 +46,7 @@ typedef enum { TOKEN_END, TOKEN_FOR, TOKEN_FUNCTION, + TOKEN_CLASS, TOKEN_IF, TOKEN_LOCAL, TOKEN_NOT, @@ -53,7 +54,6 @@ typedef enum { TOKEN_RETURN, TOKEN_THEN, TOKEN_VAR, - TOKEN_THIS, TOKEN_WHILE, TOKEN_ERROR, @@ -78,7 +78,6 @@ typedef struct { char *startChar; int line; // current line int lastLine; // line of the previous consumed token - int openedBraces; // tracks open [], {}, or () bool isEnd; CTokenType lastType; } CLexState; diff --git a/src/cmem.c b/src/cmem.c index 0a640b6..7724ddd 100644 --- a/src/cmem.c +++ b/src/cmem.c @@ -85,7 +85,9 @@ void blackenObject(CState *state, CObj *obj) { break; case COBJ_OBJECT: { // mark everything this object is keeping track of - markTable(state, &((CObjObject*)obj)->tbl); + CObjObject *cobj = (CObjObject*)obj; + markTable(state, &cobj->tbl); + markObject(state, (CObj*)cobj->meta); break; } case COBJ_UPVALUE: { @@ -99,6 +101,12 @@ void blackenObject(CState *state, CObj *obj) { break; } + case COBJ_METHOD: { + CObjMethod *method = (CObjMethod*)obj; + markObject(state, (CObj*)method->closure); + markObject(state, (CObj*)method->obj); + break; + } case COBJ_CLOSURE: { CObjClosure *closure = (CObjClosure*)obj; markObject(state, (CObj*)closure->function); @@ -200,6 +208,7 @@ void markRoots(CState *state) { } markTable(state, &state->globals); + markObject(state, (CObj*)state->initString); traceGrays(state); } diff --git a/src/cmem.h b/src/cmem.h index 353c628..203b341 100644 --- a/src/cmem.h +++ b/src/cmem.h @@ -5,7 +5,7 @@ #include "cstate.h" -//#define GC_STRESS +#define GC_STRESS //#define GC_DEBUG // arrays will grow by a factor of 2 #define GROW_FACTOR 2 diff --git a/src/cobj.c b/src/cobj.c index 2dd73b2..eef38c8 100644 --- a/src/cobj.c +++ b/src/cobj.c @@ -62,6 +62,10 @@ void cosmoO_free(CState *state, CObj* obj) { 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_CLOSURE: { CObjClosure* closure = (CObjClosure*)obj; cosmoM_freearray(state, CObjUpval*, closure->upvalues, closure->upvalueCount); @@ -88,11 +92,14 @@ bool cosmoO_equal(CObj* obj1, CObj* obj2) { } } -CObjObject *cosmoO_newObject(CState *state, int startCap) { - CObjObject *tbl = (CObjObject*)cosmoO_allocateBase(state, sizeof(CObjObject), COBJ_OBJECT); +CObjObject *cosmoO_newObject(CState *state) { + CObjObject *obj = (CObjObject*)cosmoO_allocateBase(state, sizeof(CObjObject), COBJ_OBJECT); + obj->meta = NULL; + cosmoV_pushValue(state, cosmoV_newObj(obj)); // so out GC can keep track of it + cosmoT_initTable(state, &obj->tbl, ARRAY_START); + cosmoV_pop(state); - cosmoT_initTable(state, &tbl->tbl, startCap); - return tbl; + return obj; } CObjFunction *cosmoO_newFunction(CState *state) { @@ -111,6 +118,13 @@ CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func) { return cfunc; } +CObjMethod *cosmoO_newMethod(CState *state, CObjClosure *func, CObjObject *obj) { + CObjMethod *method = (CObjMethod*)cosmoO_allocateBase(state, sizeof(CObjMethod), COBJ_METHOD); + method->closure = func; + method->obj = obj; + return method; +} + CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func) { // intialize array of pointers CObjUpval **upvalues = cosmoM_xmalloc(state, sizeof(CObjUpval*) * func->upvals); @@ -180,7 +194,11 @@ CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uin } bool cosmoO_getObject(CState *state, CObjObject *object, CValue key, CValue *val) { - return cosmoT_get(&object->tbl, key, val); + if (!cosmoT_get(&object->tbl, key, val) && object->meta != NULL) { // if the field doesn't exist in the object, check the meta + return cosmoO_getObject(state, object->meta, key, val); + } + + return true; } void cosmoO_setObject(CState *state, CObjObject *object, CValue key, CValue val) { @@ -188,22 +206,22 @@ void cosmoO_setObject(CState *state, CObjObject *object, CValue key, CValue val) *newVal = val; } -CObjString *cosmoO_toString(CState *state, CObj *val) { - switch (val->type) { +CObjString *cosmoO_toString(CState *state, CObj *obj) { + switch (obj->type) { case COBJ_STRING: { - return (CObjString*)val; + return (CObjString*)obj; } case COBJ_FUNCTION: { - CObjFunction *func = (CObjFunction*)val; + CObjFunction *func = (CObjFunction*)obj; return func->name != NULL ? func->name : cosmoO_copyString(state, UNNAMEDCHUNK, strlen(UNNAMEDCHUNK)); } 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", obj) + 1; // +1 for the null character return cosmoO_copyString(state, buf, sz); } default: - return cosmoO_copyString(state, "", 6); + return cosmoO_copyString(state, "", 10); } } @@ -229,7 +247,7 @@ void printObject(CObj *o) { if (objFunc->name != NULL) printf(" %.*s", objFunc->name->length, objFunc->name->str); else - printf(" _main"); + printf(" %s", UNNAMEDCHUNK); break; } case COBJ_CFUNCTION: { @@ -237,12 +255,21 @@ void printObject(CObj *o) { printf(" %p", objCFunc->cfunc); break; } + case COBJ_METHOD: { + CObjMethod *method = (CObjMethod*)o; + if (method->closure->function->name != NULL) { + printf(" %p : %.*s", method->obj, method->closure->function->name->length, method->closure->function->name->str); + } else { + printf(" %p : %s", method->obj, UNNAMEDCHUNK); + } + break; + } case COBJ_CLOSURE: { CObjClosure *closure = (CObjClosure*)o; printObject((CObj*)closure->function); // just print the function break; } default: - printf(""); + printf(""); } } \ No newline at end of file diff --git a/src/cobj.h b/src/cobj.h index 632fdbf..f38624c 100644 --- a/src/cobj.h +++ b/src/cobj.h @@ -13,14 +13,15 @@ typedef enum { COBJ_OBJECT, COBJ_FUNCTION, COBJ_CFUNCTION, + COBJ_METHOD, // internal use COBJ_CLOSURE, COBJ_UPVALUE, } CObjType; -#define CommonHeader CObj obj; +#define CommonHeader CObj _obj; -typedef int (*CosmoCFunction)(CState *state, int argCount, CValue *args); +typedef CValue (*CosmoCFunction)(CState *state, int argCount, CValue *args); typedef struct CObj { CObjType type; @@ -38,7 +39,7 @@ typedef struct CObjString { typedef struct CObjObject { CommonHeader; // "is a" CObj CTable tbl; - //struct CObjObject *meta; // metaobject, used to describe object behavior + struct CObjObject *meta; // metaobject, describes the behavior of the object } CObjObject; typedef struct CObjFunction { @@ -61,6 +62,12 @@ typedef struct CObjClosure { int upvalueCount; } CObjClosure; +typedef struct CObjMethod { + CommonHeader; // "is a " CObj + CObjObject *obj; // obj this method is bound too + CObjClosure *closure; // TODO: change this to a union to a pointer to a closure object or a c function object +} CObjMethod; + typedef struct CObjUpval { CommonHeader; // "is a" CObj CValue *val; @@ -72,12 +79,14 @@ typedef struct CObjUpval { #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_METHOD(x) isObjType(x, COBJ_METHOD) #define IS_CLOSURE(x) isObjType(x, COBJ_CLOSURE) #define cosmoV_readString(x) ((CObjString*)cosmoV_readObj(x)) -#define cosmoV_readObject(x) ((CObjObject*)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_readMethod(x) ((CObjMethod*)cosmoV_readObj(x)) #define cosmoV_readClosure(x) ((CObjClosure*)cosmoV_readObj(x)) static inline bool isObjType(CValue val, CObjType type) { @@ -89,9 +98,10 @@ void cosmoO_free(CState *state, CObj* obj); bool cosmoO_equal(CObj* obj1, CObj* obj2); -CObjObject *cosmoO_newObject(CState *state, int startCap); +CObjObject *cosmoO_newObject(CState *state); CObjFunction *cosmoO_newFunction(CState *state); CObjCFunction *cosmoO_newCFunction(CState *state, CosmoCFunction func); +CObjMethod *cosmoO_newMethod(CState *state, CObjClosure *func, CObjObject *obj); CObjClosure *cosmoO_newClosure(CState *state, CObjFunction *func); CObjString *cosmoO_toString(CState *state, CObj *val); CObjUpval *cosmoO_newUpvalue(CState *state, CValue *val); diff --git a/src/coperators.h b/src/coperators.h index 4a1aeb3..d346cc4 100644 --- a/src/coperators.h +++ b/src/coperators.h @@ -32,6 +32,7 @@ typedef enum { OP_NEWOBJECT, OP_GETOBJECT, OP_SETOBJECT, + OP_NEWCLASS, // ARITHMETIC OP_ADD, diff --git a/src/cosmo.h b/src/cosmo.h index 738685d..8533b76 100644 --- a/src/cosmo.h +++ b/src/cosmo.h @@ -18,6 +18,8 @@ typedef struct CObjString CObjString; typedef struct CObjUpval CObjUpval; typedef struct CObjFunction CObjFunction; typedef struct CObjCFunction CObjCFunction; +typedef struct CObjMethod CObjMethod; +typedef struct CObjClass CObjClass; typedef struct CObjClosure CObjClosure; typedef uint8_t INSTRUCTION; diff --git a/src/cparse.c b/src/cparse.c index 94301a2..afe2c7f 100644 --- a/src/cparse.c +++ b/src/cparse.c @@ -9,6 +9,36 @@ // we define all of this here because we only need it in this file, no need for it to be in the header /shrug +typedef struct { + CToken name; + int depth; + bool isCaptured; // is the Local referenced in an upvalue? +} Local; + +typedef struct { + uint8_t index; + bool isLocal; +} Upvalue; + +typedef enum { + FTYPE_FUNCTION, + FTYPE_METHOD, // a function bounded to an object (can use "this" identifer to access the current object :pog:) + FTYPE_SCRIPT +} FunctionType; + +typedef struct CCompilerState { + CObjFunction *function; + FunctionType type; + + Local locals[256]; + Upvalue upvalues[256]; + int localCount; + int scopeDepth; + int pushedValues; + int savedPushed; + struct CCompilerState* enclosing; +} CCompilerState; + typedef struct { CLexState *lex; CCompilerState* compiler; @@ -50,7 +80,7 @@ static void declaration(CParseState *pstate); static void function(CParseState *pstate, FunctionType type); static void expressionStatement(CParseState *pstate); static ParseRule* getRule(CTokenType type); -static CObjFunction *endCompiler(CParseState *pstate, int results); +static CObjFunction *endCompiler(CParseState *pstate); // ================================================================ [FRONT END/TALK TO LEXER] ================================================================ @@ -66,16 +96,15 @@ static void initCompilerState(CParseState* pstate, CCompilerState *ccstate, Func ccstate->type = type; ccstate->function = cosmoO_newFunction(pstate->state); - if (type != FTYPE_SCRIPT) ccstate->function->name = cosmoO_copyString(pstate->state, pstate->previous.start, pstate->previous.length); - // mark first local slot as used (this'll hold the CObjFunction of the current function) + // mark first local slot as used (this'll hold the CObjFunction of the current function, or if it's a method it'll hold the currently bounded object) Local *local = &ccstate->locals[ccstate->localCount++]; local->depth = 0; local->isCaptured = false; - local->name.length = 0; local->name.start = ""; + local->name.length = 0; } static void initParseState(CParseState *pstate, CCompilerState *ccstate, CState *s, const char *source) { @@ -170,6 +199,16 @@ static void inline valuePopped(CParseState *pstate, int values) { pstate->compiler->pushedValues -= values; } +static bool blockFollow(CToken token) { + switch (token.type) { + case TOKEN_END: case TOKEN_ELSE: + case TOKEN_ELSEIF: case TOKEN_EOS: + return true; + default: + return false; + } +} + // ================================================================ [WRITE TO CHUNK] ================================================================ CChunk* getChunk(CParseState *pstate) { @@ -491,22 +530,46 @@ static void call_(CParseState *pstate, bool canAssign) { valuePopped(pstate, argCount + 1); // all of these values will be popped off the stack when returned (+1 for the function) writeu8(pstate, OP_CALL); writeu8(pstate, argCount); - - // hacky hacky hacky hACKY GACJ HACK!!!!!!!! - if (pstate->compiler->pushedValues < pstate->compiler->savedPushed) { // there's empty spots on the stack waiting to be filled, lets make OP_CALL fill those spots for us - writeu8(pstate, pstate->compiler->savedPushed - pstate->compiler->pushedValues); // number of expected results - pstate->compiler->pushedValues = pstate->compiler->savedPushed; // either way the stack will be balanaced after this call - } else { - writeu8(pstate, 1); // we expect 1 result by default - valuePushed(pstate, 1); - } + valuePushed(pstate, 1); } static void object(CParseState *pstate, bool canAssign) { // already consumed the beginning '{' int entries = 0; - consume(pstate, TOKEN_RIGHT_BRACE, "Expected '}' to end object definition!"); + if (!match(pstate, TOKEN_RIGHT_BRACE)) { + do { + if (match(pstate, TOKEN_IDENTIFIER)) { + uint16_t fieldIdent = identifierConstant(pstate, &pstate->previous); + + // OP_NEWOBJECT expects the key on the stack before the value + writeu8(pstate, OP_LOADCONST); + writeu16(pstate, fieldIdent); + + consume(pstate, TOKEN_EQUAL, "Invalid syntax!"); + + // parse field + expression(pstate); + valuePopped(pstate, 1); + } else if (match(pstate, TOKEN_LEFT_BRACKET)) { + // parse the key first + expression(pstate); // should parse until end bracket + + consume(pstate, TOKEN_RIGHT_BRACKET, "Expected ']' to end index definition."); + consume(pstate, TOKEN_EQUAL, "Expected '='."); + + // now, parse the value (until comma) + expression(pstate); + valuePopped(pstate, 2); + } else { + error(pstate, "Invalid syntax!"); + } + + entries++; + } while (match(pstate, TOKEN_COMMA) && !pstate->hadError); + + consume(pstate, TOKEN_RIGHT_BRACE, "Expected '}' to end object definition."); + } writeu8(pstate, OP_NEWOBJECT); writeu8(pstate, entries); @@ -515,7 +578,7 @@ static void object(CParseState *pstate, bool canAssign) { static void dot(CParseState *pstate, bool canAssign) { consume(pstate, TOKEN_IDENTIFIER, "Expected property name after '.'."); - uint8_t name = identifierConstant(pstate, &pstate->previous); + uint16_t name = identifierConstant(pstate, &pstate->previous); writeu8(pstate, OP_LOADCONST); writeu16(pstate, name); @@ -579,6 +642,7 @@ ParseRule ruleTable[] = { [TOKEN_END] = {NULL, NULL, PREC_NONE}, [TOKEN_FOR] = {NULL, NULL, PREC_NONE}, [TOKEN_FUNCTION] = {anonFunction, NULL, PREC_NONE}, + [TOKEN_CLASS] = {NULL, NULL, PREC_NONE}, [TOKEN_IF] = {NULL, NULL, PREC_NONE}, [TOKEN_LOCAL] = {NULL, NULL, PREC_NONE}, [TOKEN_NOT] = {NULL, NULL, PREC_NONE}, @@ -588,7 +652,6 @@ ParseRule ruleTable[] = { [TOKEN_WHILE] = {NULL, NULL, PREC_NONE}, [TOKEN_ERROR] = {NULL, NULL, PREC_NONE}, [TOKEN_VAR] = {NULL, NULL, PREC_NONE}, - [TOKEN_THIS] = {NULL, NULL, PREC_NONE}, [TOKEN_EOF] = {NULL, NULL, PREC_NONE} }; @@ -601,9 +664,8 @@ static void parsePrecedence(CParseState *pstate, Precedence prec) { ParseFunc prefix = getRule(pstate->previous.type)->prefix; - if (prefix == NULL) { - return error(pstate, "Expected expression!"); - } + if (prefix == NULL) + return; bool canAssign = prec <= PREC_ASSIGNMENT; prefix(pstate, canAssign); @@ -666,6 +728,33 @@ static void defineVariable(CParseState *pstate, uint16_t global, bool forceLocal valuePopped(pstate, 1); } +static void _class(CParseState *pstate) { + uint16_t var = parseVariable(pstate, "Expected identifer!", false); + int entries = 0; + + while (!match(pstate, TOKEN_END) && !match(pstate, TOKEN_EOF) && !pstate->hadError) { + if (match(pstate, TOKEN_FUNCTION)) { + // define method + consume(pstate, TOKEN_IDENTIFIER, "Expected identifier!"); + uint16_t fieldIdent = identifierConstant(pstate, &pstate->previous); + + // OP_NEWOBJECT expects the key on the stack before the value + writeu8(pstate, OP_LOADCONST); + writeu16(pstate, fieldIdent); + + function(pstate, FTYPE_METHOD); + valuePopped(pstate, 1); + } + + entries++; + } + + writeu8(pstate, OP_NEWOBJECT); + writeu8(pstate, entries); + valuePushed(pstate, 1); + defineVariable(pstate, var, false); +} + static void popLocals(CParseState *pstate, int toScope) { if (pstate->hadError) return; @@ -708,7 +797,7 @@ static void endScope(CParseState *pstate) { // parses expressionStatements until a TOKEN_END is consumed static void block(CParseState *pstate) { - while(!check(pstate, TOKEN_END) && !check(pstate, TOKEN_EOF)) { + while(!check(pstate, TOKEN_END) && !check(pstate, TOKEN_EOF) && !check(pstate, TOKEN_ERROR)) { declaration(pstate); } @@ -760,7 +849,7 @@ static void ifStatement(CParseState *pstate) { // parse until 'end' or 'else' beginScope(pstate); - while(!check(pstate, TOKEN_END) && !check(pstate, TOKEN_ELSE) && !check(pstate, TOKEN_ELSEIF) && !check(pstate, TOKEN_EOF)) { + while(!check(pstate, TOKEN_END) && !check(pstate, TOKEN_ELSE) && !check(pstate, TOKEN_ELSEIF) && !check(pstate, TOKEN_EOF) && !check(pstate, TOKEN_ERROR)) { declaration(pstate); } @@ -828,7 +917,7 @@ static void function(CParseState *pstate, FunctionType type) { } // parse identifier for param (force them to be a local) - uint8_t funcIdent = parseVariable(pstate, "Expected identifier for function!", true); + uint16_t funcIdent = parseVariable(pstate, "Expected identifier for function!", true); defineVariable(pstate, funcIdent, true); valuePushed(pstate, 1); // they *will* be populated during runtime } while (match(pstate, TOKEN_COMMA)); @@ -840,7 +929,7 @@ static void function(CParseState *pstate, FunctionType type) { alignStack(pstate, savedPushed); endScope(pstate); - CObjFunction *objFunc = endCompiler(pstate, 0); + CObjFunction *objFunc = endCompiler(pstate); // push closure writeu8(pstate, OP_CLOSURE); @@ -855,7 +944,7 @@ static void function(CParseState *pstate, FunctionType type) { } static void functionDeclaration(CParseState *pstate) { - uint8_t var = parseVariable(pstate, "Expected identifer!", false); + uint16_t var = parseVariable(pstate, "Expected identifer!", false); if (pstate->compiler->scopeDepth > 0) markInitialized(pstate, var); @@ -871,27 +960,19 @@ static void returnStatement(CParseState *pstate) { return; } - // can return multiple results - int results = 0; - if (!check(pstate, TOKEN_EOS)) { // make sure its not an end of a statement - do { - expression(pstate); - results++; - } while (match(pstate, TOKEN_COMMA)); - - if (results > UINT8_MAX) { - error(pstate, "Too many results returned!"); - return; - } + if (blockFollow(pstate->current)) { // does this return have a value + writeu8(pstate, OP_NIL); + writeu8(pstate, OP_RETURN); + return; } + expression(pstate); writeu8(pstate, OP_RETURN); - writeu8(pstate, results); - valuePopped(pstate, results); + valuePopped(pstate, 1); } static void localFunction(CParseState *pstate) { - uint8_t var = parseVariable(pstate, "Expected identifer!", true); + uint16_t var = parseVariable(pstate, "Expected identifer!", true); markInitialized(pstate, var); function(pstate, FTYPE_FUNCTION); @@ -907,6 +988,7 @@ static void forLoop(CParseState *pstate) { // parse initalizer if (!match(pstate, TOKEN_EOS)) { expressionStatement(pstate); + consume(pstate, TOKEN_EOS, "Expected ';' after initalizer!"); } int loopStart = getChunk(pstate)->count; @@ -985,14 +1067,13 @@ static void expressionStatement(CParseState *pstate) { forLoop(pstate); } else if (match(pstate, TOKEN_FUNCTION)) { functionDeclaration(pstate); + } else if (match(pstate, TOKEN_CLASS)) { + _class(pstate); } else if (match(pstate, TOKEN_RETURN)) { returnStatement(pstate); - } else if (check(pstate, TOKEN_EOS)) { - // do nothing, just consume it } else { expression(pstate); } - consume(pstate, TOKEN_EOS, "Expected end of statement after expression."); // realign the stack alignStack(pstate, pstate->compiler->savedPushed); @@ -1010,10 +1091,10 @@ static void declaration(CParseState *pstate) { synchronize(pstate); } -static CObjFunction *endCompiler(CParseState *pstate, int results) { +static CObjFunction *endCompiler(CParseState *pstate) { popLocals(pstate, pstate->compiler->scopeDepth); // remove the locals from other scopes + writeu8(pstate, OP_NIL); writeu8(pstate, OP_RETURN); - writeu8(pstate, results); // update pstate to next compiler state CCompilerState *cachedCCState = pstate->compiler; @@ -1041,7 +1122,7 @@ CObjFunction* cosmoP_compileString(CState *state, const char *source) { popLocals(&parser, -1); // needed to close over the values 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, 0); + endCompiler(&parser); freeParseState(&parser); // the VM still expects a result on the stack TODO: push the error string to the stack @@ -1054,7 +1135,7 @@ CObjFunction* cosmoP_compileString(CState *state, const char *source) { // 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))); - endCompiler(&parser, 0); + endCompiler(&parser); freeParseState(&parser); cosmoM_unfreezeGC(state); return resFunc; diff --git a/src/cparse.h b/src/cparse.h index 8ff9c1d..0dd6b2e 100644 --- a/src/cparse.h +++ b/src/cparse.h @@ -4,35 +4,6 @@ #include "cosmo.h" #include "clex.h" -typedef struct { - CToken name; - int depth; - bool isCaptured; // is the Local referenced in an upvalue? -} Local; - -typedef struct { - uint8_t index; - bool isLocal; -} Upvalue; - -typedef enum { - FTYPE_FUNCTION, - FTYPE_SCRIPT -} FunctionType; - -typedef struct CCompilerState { - CObjFunction *function; - FunctionType type; - - Local locals[256]; - Upvalue upvalues[256]; - int localCount; - int scopeDepth; - int pushedValues; - int savedPushed; - struct CCompilerState* enclosing; -} CCompilerState; - // compiles source into CChunk, if NULL is returned, a syntaxical error has occured and pushed onto the stack CObjFunction* cosmoP_compileString(CState *state, const char *source); diff --git a/src/cstate.c b/src/cstate.c index 0723010..8ae081e 100644 --- a/src/cstate.c +++ b/src/cstate.c @@ -2,6 +2,7 @@ #include "cchunk.h" #include "cobj.h" #include "cvm.h" +#include "cmem.h" #include @@ -32,10 +33,16 @@ CState *cosmoV_newState() { cosmoT_initTable(state, &state->strings, 8); // init string table cosmoT_initTable(state, &state->globals, 8); // init global table + + state->initString = NULL; + state->initString = cosmoO_copyString(state, "__init", 6); return state; } void cosmoV_freeState(CState *state) { +#ifdef GC_DEBUG + printf("state %p is being free'd!\n", state); +#endif // frees all the objects CObj *objs = state->objects; while (objs != NULL) { @@ -45,6 +52,7 @@ void cosmoV_freeState(CState *state) { } // free our string & global table + state->initString = NULL; cosmoT_clearTable(state, &state->strings); cosmoT_clearTable(state, &state->globals); diff --git a/src/cstate.h b/src/cstate.h index 817c9c0..abeb1c6 100644 --- a/src/cstate.h +++ b/src/cstate.h @@ -6,8 +6,6 @@ #include "cobj.h" #include "ctable.h" -typedef struct CCompilerState CCompilerState; - typedef struct CCallFrame { CObjClosure *closure; INSTRUCTION *pc; @@ -32,6 +30,8 @@ typedef struct CState { CValue stack[STACK_MAX]; // stack CCallFrame callFrame[FRAME_MAX]; // call frames int frameCount; + + CObjString *initString; } CState; COSMO_API CState *cosmoV_newState(); diff --git a/src/cvalue.c b/src/cvalue.c index 2280695..800d47d 100644 --- a/src/cvalue.c +++ b/src/cvalue.c @@ -46,8 +46,11 @@ COSMO_API CObjString *cosmoV_toString(CState *state, CValue val) { case COSMO_TOBJ: { return cosmoO_toString(state, val.val.obj); } + case COSMO_TNIL: { + return cosmoO_copyString(state, "nil", 3); + } default: - return cosmoO_copyString(state, "", 6); + return cosmoO_copyString(state, "", 10); } } @@ -67,6 +70,6 @@ void printValue(CValue val) { printf("nil"); break; default: - printf(""); + printf(""); } } \ No newline at end of file diff --git a/src/cvm.c b/src/cvm.c index e9db5bd..4981b23 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -105,19 +105,40 @@ CObjString *cosmoV_concat(CState *state, CObjString *strA, CObjString *strB) { return cosmoO_takeString(state, buf, sz); } -int cosmoV_execute(CState *state); +bool cosmoV_execute(CState *state); typedef enum { CALL_CLOSURE, CALL_CFUNCTION } preCallResult; -int cosmoV_preCall(CState *state, int args, int nresults) { - return -1; +bool call(CState *state, CObjClosure *closure, int args) { + // missmatched args, thats an obvious user error, so error. + if (args != closure->function->args) { + runtimeError(state, "Expected %d parameters for %s, got %d!", closure->function->args, closure->function->name == NULL ? UNNAMEDCHUNK : closure->function->name->str, args); + return false; + } + + // load function into callframe + pushCallFrame(state, closure, closure->function->args); + + // execute + if (!cosmoV_execute(state)) + return false; + + // remember where the return value is + CValue* result = cosmoV_getTop(state, 0); + + // pop the callframe and return result :) + popCallFrame(state); + + // push the return value back onto the stack + cosmoV_pushValue(state, *result); + return true; } -// args = # of pass parameters, nresults = # of expected results -COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) { +// args = # of pass parameters +COSMOVMRESULT cosmoV_call(CState *state, int args) { StkPtr val = cosmoV_getTop(state, args); // function will always be right above the args if (!(val->type == COSMO_TOBJ)) { @@ -128,9 +149,18 @@ COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) { switch (val->val.obj->type) { case COBJ_CLOSURE: { CObjClosure *closure = (CObjClosure*)(val->val.obj); + if (!call(state, closure, args)) { + return COSMOVM_RUNTIME_ERR; + } + break; + } + case COBJ_METHOD: { + CObjMethod *method = (CObjMethod*)val->val.obj; + *val = cosmoV_newObj(method->obj); // sets the object on the stack so the function can reference it in it's first argument - // missmatched args, thats an obvious user error, so error. - if (args != closure->function->args) { + CObjClosure *closure = method->closure; + + if (args+1 != closure->function->args) { runtimeError(state, "Expected %d parameters for %s, got %d!", closure->function->args, closure->function->name == NULL ? UNNAMEDCHUNK : closure->function->name->str, args); return COSMOVM_RUNTIME_ERR; } @@ -139,22 +169,53 @@ COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) { pushCallFrame(state, closure, closure->function->args); // execute - int res = cosmoV_execute(state); + if (!cosmoV_execute(state)) + return COSMOVM_RUNTIME_ERR; + CValue* result = state->top; - // so, since we can have any # of results, we need to move the expected results to the original call frame (that means popping/adding however many results) - CValue* results = state->top; - - // pop the callframe and return result :) + // pop the callframe and return result popCallFrame(state); + state->top++; // adjust stack back into place + cosmoV_pushValue(state, *result); // and push return value + break; + } + case COBJ_OBJECT: { + CObjObject *metaObj = (CObjObject*)val->val.obj; + CObjObject *newObj = cosmoO_newObject(state); + newObj->meta = metaObj; + *val = cosmoV_newObj(newObj); + CValue ret; - // return the results to the stack - for (int i = 1; i <= nresults; i++) { - if (i <= res) - cosmoV_pushValue(state, results[-i]); - else - cosmoV_pushValue(state, cosmoV_newNil()); + // check if they defined an initalizer + if (cosmoO_getObject(state, metaObj, cosmoV_newObj(state->initString), &ret) && IS_CLOSURE(ret)) { + CObjClosure *closure = cosmoV_readClosure(ret); + + if (args+1 != closure->function->args) { + runtimeError(state, "Expected %d parameters for %s, got %d!", closure->function->args, closure->function->name == NULL ? UNNAMEDCHUNK : closure->function->name->str, args); + return COSMOVM_RUNTIME_ERR; + } + + // load function into callframe + pushCallFrame(state, closure, closure->function->args); + + // execute + if (!cosmoV_execute(state)) + return COSMOVM_RUNTIME_ERR; + + // we throw away the return result, it's unused + // pop the callframe and return result :) + popCallFrame(state); + state->top++; // adjust stack back into place + } else { + // no default initalizer + if (args != 0) { + runtimeError(state, "Expected 0 parameters, got %d!", args); + return COSMOVM_RUNTIME_ERR; + } + state->top--; } + cosmoV_pushValue(state, cosmoV_newObj(newObj)); break; } case COBJ_CFUNCTION: { @@ -163,21 +224,11 @@ COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) { CValue *savedBase = state->top - args - 1; cosmoM_freezeGC(state); // we don't want a GC event during c api because we don't actually trust the user to know how to evade the GC - int res = cfunc(state, args, state->top - args); + CValue res = cfunc(state, args, state->top - args); cosmoM_unfreezeGC(state); - // so, since we can have any # of results, we need to move the expected results to the original call frame - CValue* results = state->top; state->top = savedBase; - - // return the results to the stack - for (int i = 1; i <= nresults; i++) { - if (i <= res) - cosmoV_pushValue(state, results[-i]); - else - cosmoV_pushValue(state, cosmoV_newNil()); - } - + cosmoV_pushValue(state, res); break; } default: @@ -199,12 +250,11 @@ static inline bool isFalsey(StkPtr val) { cosmoV_setTop(state, 2); /* pop the 2 values */ \ cosmoV_pushValue(state, typeConst((valA->val.num) op (valB->val.num))); \ } else { \ - runtimeError(state, "Expected number! got %d and %d", valA->type, valB->type); \ + runtimeError(state, "Expected number!"); \ } \ - -// returns -1 if error, otherwise returns ammount of results -int cosmoV_execute(CState *state) { +// returns false if panic +bool cosmoV_execute(CState *state) { CCallFrame* frame = &state->callFrame[state->frameCount - 1]; // grabs the current frame CValue *constants = frame->closure->function->chunk.constants.values; // cache the pointer :) @@ -285,8 +335,7 @@ int cosmoV_execute(CState *state) { } case OP_CALL: { uint8_t args = READBYTE(); - uint8_t results = READBYTE(); - COSMOVMRESULT result = cosmoV_call(state, args, results); + COSMOVMRESULT result = cosmoV_call(state, args); if (result != COSMOVM_OK) { return result; } @@ -320,12 +369,12 @@ int cosmoV_execute(CState *state) { 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 + CObjObject *newObj = cosmoO_newObject(state); // 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); + val = cosmoV_getTop(state, (i*2) + 1); + key = cosmoV_getTop(state, (i*2) + 2); // set key/value pair CValue *newVal = cosmoT_insert(state, &newObj->tbl, *key); @@ -351,6 +400,10 @@ int cosmoV_execute(CState *state) { CValue val; // to hold our value cosmoO_getObject(state, object, *key, &val); + if (IS_CLOSURE(val)) { // is it a function? if so, make it a method to the current object + CObjMethod *method = cosmoO_newMethod(state, cosmoV_readClosure(val), object); + val = cosmoV_newObj(method); + } cosmoV_setTop(state, 2); // pops the object & the key cosmoV_pushValue(state, val); // pushes the field result break; @@ -453,8 +506,7 @@ int cosmoV_execute(CState *state) { case OP_FALSE: cosmoV_pushValue(state, cosmoV_newBoolean(false)); break; case OP_NIL: cosmoV_pushValue(state, cosmoV_newNil()); break; case OP_RETURN: { - uint8_t results = READBYTE(); - return results; + return true; } default: CERROR("unknown opcode!"); @@ -467,7 +519,7 @@ int cosmoV_execute(CState *state) { #undef READUINT // we'll only reach this is state->panic is true - return COSMOVM_RUNTIME_ERR; + return false; } #undef BINARYOP \ No newline at end of file diff --git a/src/cvm.h b/src/cvm.h index ee63609..cd47006 100644 --- a/src/cvm.h +++ b/src/cvm.h @@ -11,6 +11,6 @@ typedef enum { } COSMOVMRESULT; // args = # of pass parameters, nresults = # of expected results -COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults); +COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args); #endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 28db1ed..1823a7e 100644 --- a/src/main.c +++ b/src/main.c @@ -7,12 +7,12 @@ #include "cmem.h" -static bool _ACTIVE; +static bool _ACTIVE = false; -int cosmoB_quitRepl(CState *state, int nargs, CValue *args) { +CValue cosmoB_quitRepl(CState *state, int nargs, CValue *args) { _ACTIVE = false; - return 0; // we don't do anything to the stack + return cosmoV_newNil(); // we don't return anything } static void interpret(CState *state, const char* script) { @@ -22,11 +22,10 @@ static void interpret(CState *state, const char* script) { if (func != NULL) { disasmChunk(&func->chunk, "_main", 0); - cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected + COSMOVMRESULT res = cosmoV_call(state, 0); // 0 args being passed - //cosmoV_printStack(state); - //cosmoT_printTable(&state->globals, "globals"); - //cosmoT_printTable(&state->strings, "strings"); + if (res == COSMOVM_RUNTIME_ERR) + state->panic = false; // so our repl isn't broken } } diff --git a/test.cosmo b/test.cosmo index fd9f663..eb73b10 100644 --- a/test.cosmo +++ b/test.cosmo @@ -1,7 +1,26 @@ -var test = {} +class test + function __init(self, str) + self.hello = str + end -if !test.hello then - test.hello = "hello world!" + function print(self, i) + local str = self.hello + + for (var x = i; x > 0; x=x-1) do + str = str .. "!" + end + + print(str) + end end -print(test.hello .. "!!!!") \ No newline at end of file +var obj = test("Hello world") +for (var i = 1; i <= 10; i=i+1) do + obj.print(i) +end + +test.debug = function(self) + print("hi from " .. self) +end + +obj.debug() \ No newline at end of file