mirror of
https://github.com/CPunch/Cosmo.git
synced 2024-11-05 08: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);
|
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:
|
||||||
|
@ -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},
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
76
src/cparse.c
76
src/cparse.c
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
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
|
// 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;
|
||||||
|
Loading…
Reference in New Issue
Block a user