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 <object> do ... end).
savedPushed has been removed from the CCompilerState struct.
This commit is contained in:
CPunch 2020-12-15 21:21:51 -06:00
parent 4f3f594b82
commit e5eca7bed6
9 changed files with 181 additions and 9 deletions

22
examples/iterator.cosmo Normal file
View File

@ -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

View File

@ -132,6 +132,10 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
return simpleInstruction("OP_SETOBJECT", offset); return simpleInstruction("OP_SETOBJECT", offset);
case OP_INVOKE: case OP_INVOKE:
return u8u8OperandInstruction("OP_INVOKE", chunk, offset); 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: case OP_ADD:
return simpleInstruction("OP_ADD", offset); return simpleInstruction("OP_ADD", offset);
case OP_SUB: case OP_SUB:

View File

@ -13,6 +13,7 @@ CReservedWord reservedWords[] = {
{TOKEN_FOR, "for", 3}, {TOKEN_FOR, "for", 3},
{TOKEN_FUNCTION, "function", 8}, {TOKEN_FUNCTION, "function", 8},
{TOKEN_IF, "if", 2}, {TOKEN_IF, "if", 2},
{TOKEN_IN, "in", 2},
{TOKEN_LOCAL, "local", 5}, {TOKEN_LOCAL, "local", 5},
{TOKEN_NIL, "nil", 3}, {TOKEN_NIL, "nil", 3},
{TOKEN_NOT, "not", 3}, {TOKEN_NOT, "not", 3},

View File

@ -52,6 +52,7 @@ typedef enum {
TOKEN_FUNCTION, TOKEN_FUNCTION,
TOKEN_PROTO, TOKEN_PROTO,
TOKEN_IF, TOKEN_IF,
TOKEN_IN,
TOKEN_LOCAL, TOKEN_LOCAL,
TOKEN_NOT, TOKEN_NOT,
TOKEN_OR, TOKEN_OR,

View File

@ -36,6 +36,8 @@ typedef enum {
OP_SETOBJECT, OP_SETOBJECT,
OP_GETOBJECT, OP_GETOBJECT,
OP_INVOKE, 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 // ARITHMETIC
OP_ADD, OP_ADD,

View File

@ -35,7 +35,6 @@ typedef struct CCompilerState {
int localCount; int localCount;
int scopeDepth; int scopeDepth;
int pushedValues; int pushedValues;
int savedPushed;
int expectedValues; int expectedValues;
struct CCompilerState* enclosing; struct CCompilerState* enclosing;
} CCompilerState; } CCompilerState;
@ -94,7 +93,6 @@ static void initCompilerState(CParseState* pstate, CCompilerState *ccstate, Func
ccstate->localCount = 0; ccstate->localCount = 0;
ccstate->scopeDepth = 0; ccstate->scopeDepth = 0;
ccstate->pushedValues = 0; ccstate->pushedValues = 0;
ccstate->savedPushed = 0;
ccstate->expectedValues = 0; ccstate->expectedValues = 0;
ccstate->type = type; ccstate->type = type;
ccstate->function = cosmoO_newFunction(pstate->state); ccstate->function = cosmoO_newFunction(pstate->state);
@ -362,6 +360,7 @@ static void alignStack(CParseState *pstate, int alignment) {
writePop(pstate, pstate->compiler->pushedValues - alignment); writePop(pstate, pstate->compiler->pushedValues - alignment);
} else if (pstate->compiler->pushedValues < alignment) { } else if (pstate->compiler->pushedValues < alignment) {
error(pstate, "Missing expression!"); error(pstate, "Missing expression!");
printf("%d < %d\n", pstate->compiler->pushedValues, alignment);
} }
pstate->compiler->pushedValues = alignment; pstate->compiler->pushedValues = alignment;
@ -746,6 +745,7 @@ ParseRule ruleTable[] = {
[TOKEN_FUNCTION] = {anonFunction, NULL, PREC_NONE}, [TOKEN_FUNCTION] = {anonFunction, NULL, PREC_NONE},
[TOKEN_PROTO] = {NULL, NULL, PREC_NONE}, [TOKEN_PROTO] = {NULL, NULL, PREC_NONE},
[TOKEN_IF] = {NULL, NULL, PREC_NONE}, [TOKEN_IF] = {NULL, NULL, PREC_NONE},
[TOKEN_IN] = {NULL, NULL, PREC_NONE},
[TOKEN_LOCAL] = {NULL, NULL, PREC_NONE}, [TOKEN_LOCAL] = {NULL, NULL, PREC_NONE},
[TOKEN_NOT] = {NULL, NULL, PREC_NONE}, [TOKEN_NOT] = {NULL, NULL, PREC_NONE},
[TOKEN_OR] = {NULL, or_, PREC_OR}, [TOKEN_OR] = {NULL, or_, PREC_OR},
@ -1091,7 +1091,75 @@ static void localFunction(CParseState *pstate) {
defineVariable(pstate, var, true); 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) { 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); beginScope(pstate);
consume(pstate, TOKEN_LEFT_PAREN, "Expected '(' after 'for'"); 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) { static void expressionStatement(CParseState *pstate) {
pstate->compiler->savedPushed = pstate->compiler->pushedValues; int savedPushed = pstate->compiler->pushedValues;
if (match(pstate, TOKEN_VAR)) { if (match(pstate, TOKEN_VAR)) {
varDeclaration(pstate, false, 0); varDeclaration(pstate, false, 0);
@ -1205,7 +1273,7 @@ static void expressionStatement(CParseState *pstate) {
} }
// realign the stack // realign the stack
alignStack(pstate, pstate->compiler->savedPushed); alignStack(pstate, savedPushed);
} }
static void statement(CParseState *pstate) { static void statement(CParseState *pstate) {

View File

@ -49,6 +49,10 @@ CState *cosmoV_newState() {
state->iStrings[ISTRING_GETTER] = cosmoO_copyString(state, "__getter", 8); state->iStrings[ISTRING_GETTER] = cosmoO_copyString(state, "__getter", 8);
state->iStrings[ISTRING_SETTER] = cosmoO_copyString(state, "__setter", 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 // set the IString flags
for (int i = 0; i < ISTRING_MAX; i++) for (int i = 0; i < ISTRING_MAX; i++)
state->iStrings[i]->isIString = true; state->iStrings[i]->isIString = true;

View File

@ -18,6 +18,8 @@ typedef enum IStringEnum {
ISTRING_NEWINDEX, // __newindex ISTRING_NEWINDEX, // __newindex
ISTRING_GETTER, // __getter ISTRING_GETTER, // __getter
ISTRING_SETTER, // __setter ISTRING_SETTER, // __setter
ISTRING_ITER, // __iter
ISTRING_NEXT, // __next
ISTRING_MAX ISTRING_MAX
} IStringEnum; } IStringEnum;

View File

@ -43,6 +43,8 @@ void cosmoV_error(CState *state, const char *format, ...) {
// TODO: push error onto the stack :P // TODO: push error onto the stack :P
state->panic = true; state->panic = true;
cosmoV_printStack(state);
} }
CObjUpval *captureUpvalue(CState *state, CValue *local) { CObjUpval *captureUpvalue(CState *state, CValue *local) {
@ -552,6 +554,72 @@ int cosmoV_execute(CState *state) {
invokeMethod(state, object, val, args, nres, 0); invokeMethod(state, object, val, args, nres, 0);
break; 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 case OP_ADD: { // pop 2 values off the stack & try to add them together
NUMBEROP(cosmoV_newNumber, +); NUMBEROP(cosmoV_newNumber, +);
break; break;