mirror of
https://github.com/CPunch/Cosmo.git
synced 2024-11-21 23:10:05 +00:00
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:
parent
4f3f594b82
commit
e5eca7bed6
22
examples/iterator.cosmo
Normal file
22
examples/iterator.cosmo
Normal 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
|
@ -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:
|
||||
|
@ -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},
|
||||
|
@ -52,6 +52,7 @@ typedef enum {
|
||||
TOKEN_FUNCTION,
|
||||
TOKEN_PROTO,
|
||||
TOKEN_IF,
|
||||
TOKEN_IN,
|
||||
TOKEN_LOCAL,
|
||||
TOKEN_NOT,
|
||||
TOKEN_OR,
|
||||
|
@ -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,
|
||||
|
76
src/cparse.c
76
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) {
|
||||
|
@ -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;
|
||||
|
12
src/cstate.h
12
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;
|
||||
|
||||
|
68
src/cvm.c
68
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;
|
||||
|
Loading…
Reference in New Issue
Block a user