From e5eca7bed6275183748fce7cbd571749014b8dca Mon Sep 17 00:00:00 2001 From: CPunch Date: Tue, 15 Dec 2020 21:21:51 -0600 Subject: [PATCH] Added iterable objects __iter and __next are now reserved IStrings, OP_NEXT and OP_ITER have also been added. A new token (TOKEN_IN) has been added to the lexer. The parser now supports the for each loop (for i, ... in do ... end). savedPushed has been removed from the CCompilerState struct. --- examples/iterator.cosmo | 22 ++++++++++++ src/cdebug.c | 4 +++ src/clex.c | 1 + src/clex.h | 1 + src/coperators.h | 2 ++ src/cparse.c | 76 ++++++++++++++++++++++++++++++++++++++--- src/cstate.c | 4 +++ src/cstate.h | 12 ++++--- src/cvm.c | 68 ++++++++++++++++++++++++++++++++++++ 9 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 examples/iterator.cosmo diff --git a/examples/iterator.cosmo b/examples/iterator.cosmo new file mode 100644 index 0000000..789c686 --- /dev/null +++ b/examples/iterator.cosmo @@ -0,0 +1,22 @@ +proto Range + function __init(self, x) + self.max = x + end + + function __iter(self) + self.i = 0 + return self + end + + function __next(self) + if self.i >= self.max then + return nil // exit iterator loop + end + + return self.i++, self.i + end +end + +for i, x in Range(10) do + print("was " .. i .. ", now " .. x) +end \ No newline at end of file diff --git a/src/cdebug.c b/src/cdebug.c index caa9043..3bd51c5 100644 --- a/src/cdebug.c +++ b/src/cdebug.c @@ -132,6 +132,10 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { return simpleInstruction("OP_SETOBJECT", offset); case OP_INVOKE: return u8u8OperandInstruction("OP_INVOKE", chunk, offset); + case OP_ITER: + return simpleInstruction("OP_ITER", offset); + case OP_NEXT: + return u8u16OperandInstruction("OP_NEXT", chunk, offset); case OP_ADD: return simpleInstruction("OP_ADD", offset); case OP_SUB: diff --git a/src/clex.c b/src/clex.c index c386437..aaa13bf 100644 --- a/src/clex.c +++ b/src/clex.c @@ -13,6 +13,7 @@ CReservedWord reservedWords[] = { {TOKEN_FOR, "for", 3}, {TOKEN_FUNCTION, "function", 8}, {TOKEN_IF, "if", 2}, + {TOKEN_IN, "in", 2}, {TOKEN_LOCAL, "local", 5}, {TOKEN_NIL, "nil", 3}, {TOKEN_NOT, "not", 3}, diff --git a/src/clex.h b/src/clex.h index 8963d8b..c082359 100644 --- a/src/clex.h +++ b/src/clex.h @@ -52,6 +52,7 @@ typedef enum { TOKEN_FUNCTION, TOKEN_PROTO, TOKEN_IF, + TOKEN_IN, TOKEN_LOCAL, TOKEN_NOT, TOKEN_OR, diff --git a/src/coperators.h b/src/coperators.h index e30419b..da40370 100644 --- a/src/coperators.h +++ b/src/coperators.h @@ -36,6 +36,8 @@ typedef enum { OP_SETOBJECT, OP_GETOBJECT, OP_INVOKE, + OP_ITER, // if stack[top] is an object, __iter is expected and called, else if stack[top] is a dictionary a dummy iterator object is made (SEE: cosmoV_makeIter()) + OP_NEXT, // if stack[top] is an object, __next is expected and called, expecting uint8_t return values. if stack[top] after calling is nil, jump uint16_t // ARITHMETIC OP_ADD, diff --git a/src/cparse.c b/src/cparse.c index 1ea6451..d2c3285 100644 --- a/src/cparse.c +++ b/src/cparse.c @@ -35,7 +35,6 @@ typedef struct CCompilerState { int localCount; int scopeDepth; int pushedValues; - int savedPushed; int expectedValues; struct CCompilerState* enclosing; } CCompilerState; @@ -94,7 +93,6 @@ static void initCompilerState(CParseState* pstate, CCompilerState *ccstate, Func ccstate->localCount = 0; ccstate->scopeDepth = 0; ccstate->pushedValues = 0; - ccstate->savedPushed = 0; ccstate->expectedValues = 0; ccstate->type = type; ccstate->function = cosmoO_newFunction(pstate->state); @@ -362,6 +360,7 @@ static void alignStack(CParseState *pstate, int alignment) { writePop(pstate, pstate->compiler->pushedValues - alignment); } else if (pstate->compiler->pushedValues < alignment) { error(pstate, "Missing expression!"); + printf("%d < %d\n", pstate->compiler->pushedValues, alignment); } pstate->compiler->pushedValues = alignment; @@ -746,6 +745,7 @@ ParseRule ruleTable[] = { [TOKEN_FUNCTION] = {anonFunction, NULL, PREC_NONE}, [TOKEN_PROTO] = {NULL, NULL, PREC_NONE}, [TOKEN_IF] = {NULL, NULL, PREC_NONE}, + [TOKEN_IN] = {NULL, NULL, PREC_NONE}, [TOKEN_LOCAL] = {NULL, NULL, PREC_NONE}, [TOKEN_NOT] = {NULL, NULL, PREC_NONE}, [TOKEN_OR] = {NULL, or_, PREC_OR}, @@ -1091,7 +1091,75 @@ static void localFunction(CParseState *pstate) { defineVariable(pstate, var, true); } +static void forEachLoop(CParseState *pstate) { + beginScope(pstate); + + // mark a slot on the stack as reserved, we do this by declaring a local with no identifer + Local *local = &pstate->compiler->locals[pstate->compiler->localCount++]; + local->depth = pstate->compiler->scopeDepth; + local->isCaptured = false; + local->name.start = ""; + local->name.length = 0; + + // how many values does it expect the iterator to return? + beginScope(pstate); + int values = 0; + do { + uint16_t funcIdent = parseVariable(pstate, "Expected identifier!", true); + defineVariable(pstate, funcIdent, true); + values++; + } while (match(pstate, TOKEN_COMMA)); + + if (values > UINT8_MAX) { + error(pstate, "Too many values expected!"); + return; + } + + // after we consume the values, get the dictionary/object/whatever on the stack + consume(pstate, TOKEN_IN, "Expected 'in' before iterator!"); + expression(pstate, 1, true); + + consume(pstate, TOKEN_DO, "Expected 'do' before loop block!"); + + writeu8(pstate, OP_ITER); // checks if stack[top] is iterable and makes an iterable object wrapper for CObjs like CObjDict + + int loopStart = getChunk(pstate)->count; + + // OP_NEXT expected a uint8_t after the opcode for how many values __next is expected to return + writeu8(pstate, OP_NEXT); + writeu8(pstate, values); + + // after the u8, is a u16 with how far to jump if __next returns nil + int jmpPatch = getChunk(pstate)->count; + writeu16(pstate, 0xFFFF); // placeholder, we'll patch this later + + // OP_NEXT pushes the values needed + valuePushed(pstate, values); + + // compile loop block + beginScope(pstate); + block(pstate); + endScope(pstate); + + // pop all of the values, OP_NEXT will repopulate them + endScope(pstate); + + // write jmp back to the start of the loop + writeJmpBack(pstate, loopStart); + patchJmp(pstate, jmpPatch); // and finally, patch our OP_NEXT + + // remove reserved local + endScope(pstate); + valuePopped(pstate, 1); +} + static void forLoop(CParseState *pstate) { + // first, check if the next token is an identifier. if it is, this is a for loop for an iterator + if (check(pstate, TOKEN_IDENTIFIER)) { + forEachLoop(pstate); + return; + } + beginScope(pstate); consume(pstate, TOKEN_LEFT_PAREN, "Expected '(' after 'for'"); @@ -1173,7 +1241,7 @@ static int expression(CParseState *pstate, int needed, bool forceNeeded) { } static void expressionStatement(CParseState *pstate) { - pstate->compiler->savedPushed = pstate->compiler->pushedValues; + int savedPushed = pstate->compiler->pushedValues; if (match(pstate, TOKEN_VAR)) { varDeclaration(pstate, false, 0); @@ -1205,7 +1273,7 @@ static void expressionStatement(CParseState *pstate) { } // realign the stack - alignStack(pstate, pstate->compiler->savedPushed); + alignStack(pstate, savedPushed); } static void statement(CParseState *pstate) { diff --git a/src/cstate.c b/src/cstate.c index c700a2a..0a9fd4c 100644 --- a/src/cstate.c +++ b/src/cstate.c @@ -49,6 +49,10 @@ CState *cosmoV_newState() { state->iStrings[ISTRING_GETTER] = cosmoO_copyString(state, "__getter", 8); state->iStrings[ISTRING_SETTER] = cosmoO_copyString(state, "__setter", 8); + // for iterators + state->iStrings[ISTRING_ITER] = cosmoO_copyString(state, "__iter", 6); + state->iStrings[ISTRING_NEXT] = cosmoO_copyString(state, "__next", 6); + // set the IString flags for (int i = 0; i < ISTRING_MAX; i++) state->iStrings[i]->isIString = true; diff --git a/src/cstate.h b/src/cstate.h index 9799f4e..84c6e70 100644 --- a/src/cstate.h +++ b/src/cstate.h @@ -13,11 +13,13 @@ typedef struct CCallFrame { } CCallFrame; typedef enum IStringEnum { - ISTRING_INIT, // __init - ISTRING_INDEX, // __index - ISTRING_NEWINDEX, // __newindex - ISTRING_GETTER, // __getter - ISTRING_SETTER, // __setter + ISTRING_INIT, // __init + ISTRING_INDEX, // __index + ISTRING_NEWINDEX, // __newindex + ISTRING_GETTER, // __getter + ISTRING_SETTER, // __setter + ISTRING_ITER, // __iter + ISTRING_NEXT, // __next ISTRING_MAX } IStringEnum; diff --git a/src/cvm.c b/src/cvm.c index f221257..add519b 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -43,6 +43,8 @@ void cosmoV_error(CState *state, const char *format, ...) { // TODO: push error onto the stack :P state->panic = true; + + cosmoV_printStack(state); } CObjUpval *captureUpvalue(CState *state, CValue *local) { @@ -552,6 +554,72 @@ int cosmoV_execute(CState *state) { invokeMethod(state, object, val, args, nres, 0); break; } + case OP_ITER: { + StkPtr temp = cosmoV_getTop(state, 0); // should be the object/dictionary + + if (!IS_OBJ(*temp)) { + cosmoV_error(state, "Couldn't iterate over non-iterator type %s!", cosmoV_typeStr(*temp)); + break; + } + + switch (cosmoV_readObj(*temp)->type) { + case COBJ_DICT: { + //CObjDict *dict = (CObjDict*)cosmoV_readObj(*temp); + + // TODO: add cosmoV_makeIter, which will make a dummy iterable object for dictionaries + cosmoV_error(state, "unimpl. mass ping cpunch!!!!"); + break; + } + case COBJ_OBJECT: { + CObjObject *obj = (CObjObject*)cosmoV_readObj(*temp); + CValue val; + + // grab __iter & call it + if (cosmoO_getIString(state, obj, ISTRING_ITER, &val)) { + cosmoV_pop(state); // pop the object from the stack + cosmoV_pushValue(state, val); + cosmoV_pushValue(state, cosmoV_newObj(obj)); + cosmoV_call(state, 1, 1); // we expect 1 return value on the stack, the iterable object + } else { + cosmoV_error(state, "Expected iterable object! '__iter' not defined!"); + } + + break; + } + default: { + cosmoV_error(state, "Couldn't iterate over non-iterator type %s!", cosmoV_typeStr(*temp)); + break; + } + } + break; + } + case OP_NEXT: { + uint8_t nresults = READBYTE(); + uint16_t jump = READUINT(); + StkPtr temp = cosmoV_getTop(state, 0); // we don't actually pop this off the stack + + if (!IS_OBJECT(*temp)) { + cosmoV_error(state, "Couldn't iterate over non-iterator type %s!", cosmoV_typeStr(*temp)); + break; + } + + CObjObject *obj = (CObjObject*)cosmoV_readObj(*temp); + CValue val; + + if (cosmoO_getIString(state, obj, ISTRING_NEXT, &val)) { + cosmoV_pushValue(state, val); + cosmoV_pushValue(state, *temp); + cosmoV_call(state, 1, nresults); + + if (IS_NIL(*(cosmoV_getTop(state, 0)))) { // __next returned a nil, which means to exit the loop + cosmoV_setTop(state, nresults); // pop the return values + frame->pc += jump; + } + } else { + cosmoV_error(state, "Expected iterable object! '__next' not defined!"); + } + break; + } case OP_ADD: { // pop 2 values off the stack & try to add them together NUMBEROP(cosmoV_newNumber, +); break;