added "break" and "continue" statements

a LoopState was added to the CCompilerState struct which keeps track of breaks, start chunk index, and the start scope of the active loop.
Also, break.cosmo was added to the examples directory, 'continue' and 'break' work as expected.
This commit is contained in:
CPunch 2020-12-24 00:41:00 -06:00
parent 31a852a127
commit a408353c25
4 changed files with 127 additions and 7 deletions

26
examples/break.cosmo Normal file
View File

@ -0,0 +1,26 @@
// just testing continues and breaks
for (var x = 0; x < 700; x++) do
for (var i = 0; true; i++) do
var str = i .. "." .. x
if (i == 998) then
print(i .. " reached")
break
end
print("for cont- " .. str)
continue
end
var i = 0
while true do
var str = i .. "." .. x
if (i++ == 1034) then
print("done")
break
end
print("while cont- " .. str)
continue
end
end

View File

@ -5,6 +5,8 @@
CReservedWord reservedWords[] = { CReservedWord reservedWords[] = {
{TOKEN_AND, "and", 3}, {TOKEN_AND, "and", 3},
{TOKEN_BREAK, "break", 5},
{TOKEN_CONTINUE, "continue", 8},
{TOKEN_DO, "do", 2}, {TOKEN_DO, "do", 2},
{TOKEN_ELSE, "else", 4}, {TOKEN_ELSE, "else", 4},
{TOKEN_ELSEIF, "elseif", 6}, {TOKEN_ELSEIF, "elseif", 6},

View File

@ -44,6 +44,8 @@ typedef enum {
// keywords & reserved words // keywords & reserved words
TOKEN_AND, TOKEN_AND,
TOKEN_BREAK,
TOKEN_CONTINUE,
TOKEN_DO, TOKEN_DO,
TOKEN_ELSE, TOKEN_ELSE,
TOKEN_ELSEIF, TOKEN_ELSEIF,

View File

@ -20,6 +20,14 @@ typedef struct {
bool isLocal; bool isLocal;
} Upvalue; } Upvalue;
typedef struct {
int *breaks; // this array is dynamically allocated
int scope; // if -1, there is no loop
int startBytecode; // start index in the chunk of the loop
int breakCount; // # of breaks to patch
int breakCapacity;
} LoopState;
typedef enum { typedef enum {
FTYPE_FUNCTION, FTYPE_FUNCTION,
FTYPE_METHOD, // a function bounded to an object (can use "this" identifer to access the current object :pog:) FTYPE_METHOD, // a function bounded to an object (can use "this" identifer to access the current object :pog:)
@ -27,11 +35,12 @@ typedef enum {
} FunctionType; } FunctionType;
typedef struct CCompilerState { typedef struct CCompilerState {
CObjFunction *function;
FunctionType type;
Local locals[256]; Local locals[256];
Upvalue upvalues[256]; Upvalue upvalues[256];
LoopState loop;
CObjFunction *function;
FunctionType type;
int localCount; int localCount;
int scopeDepth; int scopeDepth;
int pushedValues; int pushedValues;
@ -98,6 +107,8 @@ static void initCompilerState(CParseState* pstate, CCompilerState *ccstate, Func
ccstate->function = cosmoO_newFunction(pstate->state); ccstate->function = cosmoO_newFunction(pstate->state);
ccstate->function->module = pstate->module; ccstate->function->module = pstate->module;
ccstate->loop.scope = -1; // there is no loop yet
if (type != FTYPE_SCRIPT) if (type != FTYPE_SCRIPT)
ccstate->function->name = cosmoO_copyString(pstate->state, pstate->previous.start, pstate->previous.length); ccstate->function->name = cosmoO_copyString(pstate->state, pstate->previous.start, pstate->previous.length);
else else
@ -737,6 +748,8 @@ ParseRule ruleTable[] = {
[TOKEN_TRUE] = {literal, NULL, PREC_NONE}, [TOKEN_TRUE] = {literal, NULL, PREC_NONE},
[TOKEN_FALSE] = {literal, NULL, PREC_NONE}, [TOKEN_FALSE] = {literal, NULL, PREC_NONE},
[TOKEN_AND] = {NULL, and_, PREC_AND}, [TOKEN_AND] = {NULL, and_, PREC_AND},
[TOKEN_BREAK] = {NULL, NULL, PREC_NONE},
[TOKEN_CONTINUE] = {NULL, NULL, PREC_NONE},
[TOKEN_DO] = {NULL, NULL, PREC_NONE}, [TOKEN_DO] = {NULL, NULL, PREC_NONE},
[TOKEN_ELSE] = {NULL, NULL, PREC_NONE}, [TOKEN_ELSE] = {NULL, NULL, PREC_NONE},
[TOKEN_ELSEIF] = {NULL, NULL, PREC_NONE}, [TOKEN_ELSEIF] = {NULL, NULL, PREC_NONE},
@ -984,8 +997,30 @@ static void ifStatement(CParseState *pstate) {
} }
} }
static void startLoop(CParseState *pstate) {
LoopState *lstate = &pstate->compiler->loop;
lstate->scope = pstate->compiler->scopeDepth;
lstate->breaks = cosmoM_xmalloc(pstate->state, sizeof(int) * ARRAY_START);
lstate->breakCount = 0;
lstate->breakCapacity = ARRAY_START;
lstate->startBytecode = getChunk(pstate)->count;
}
// this patches all the breaks
static void endLoop(CParseState *pstate) {
while (pstate->compiler->loop.breakCount > 0) {
patchJmp(pstate, pstate->compiler->loop.breaks[--pstate->compiler->loop.breakCount]);
}
cosmoM_freearray(pstate->state, int, pstate->compiler->loop.breaks, pstate->compiler->loop.breakCapacity);
}
static void whileStatement(CParseState *pstate) { static void whileStatement(CParseState *pstate) {
LoopState cachedLoop = pstate->compiler->loop;
startLoop(pstate);
int jumpLocation = getChunk(pstate)->count; int jumpLocation = getChunk(pstate)->count;
// get conditional
expression(pstate, 1, true); expression(pstate, 1, true);
consume(pstate, TOKEN_DO, "expected 'do' after conditional expression."); consume(pstate, TOKEN_DO, "expected 'do' after conditional expression.");
@ -996,8 +1031,11 @@ static void whileStatement(CParseState *pstate) {
beginScope(pstate); beginScope(pstate);
block(pstate); // parse until 'end' block(pstate); // parse until 'end'
endScope(pstate); endScope(pstate);
writeJmpBack(pstate, jumpLocation); writeJmpBack(pstate, jumpLocation);
// patch all the breaks, and restore the previous loop state
endLoop(pstate);
pstate->compiler->loop = cachedLoop;
patchJmp(pstate, exitJump); patchJmp(pstate, exitJump);
} }
@ -1123,6 +1161,10 @@ static void forEachLoop(CParseState *pstate) {
writeu8(pstate, OP_ITER); // checks if stack[top] is iterable and pushes the __next method onto the stack for OP_NEXT to call writeu8(pstate, OP_ITER); // checks if stack[top] is iterable and pushes the __next method onto the stack for OP_NEXT to call
// start loop scope
LoopState cachedLoop = pstate->compiler->loop;
startLoop(pstate);
pstate->compiler->loop.scope--; // scope should actually be 1 less than this
int loopStart = getChunk(pstate)->count; int loopStart = getChunk(pstate)->count;
// OP_NEXT expected a uint8_t after the opcode for how many values __next is expected to return // OP_NEXT expected a uint8_t after the opcode for how many values __next is expected to return
@ -1137,15 +1179,17 @@ static void forEachLoop(CParseState *pstate) {
valuePushed(pstate, values); valuePushed(pstate, values);
// compile loop block // compile loop block
beginScope(pstate);
block(pstate); block(pstate);
endScope(pstate);
// pop all of the values, OP_NEXT will repopulate them // pop all of the values, OP_NEXT will repopulate them
endScope(pstate); endScope(pstate);
// write jmp back to the start of the loop // write jmp back to the start of the loop
writeJmpBack(pstate, loopStart); writeJmpBack(pstate, loopStart);
// patch all the breaks, and restore the previous loop state
endLoop(pstate);
pstate->compiler->loop = cachedLoop;
patchJmp(pstate, jmpPatch); // and finally, patch our OP_NEXT patchJmp(pstate, jmpPatch); // and finally, patch our OP_NEXT
// remove reserved local // remove reserved local
@ -1170,8 +1214,11 @@ static void forLoop(CParseState *pstate) {
consume(pstate, TOKEN_EOS, "Expected ';' after initializer!"); consume(pstate, TOKEN_EOS, "Expected ';' after initializer!");
} }
// start loop scope
LoopState cachedLoop = pstate->compiler->loop;
startLoop(pstate);
int loopStart = getChunk(pstate)->count; int loopStart = getChunk(pstate)->count;
// parse conditional // parse conditional
int exitJmp = -1; int exitJmp = -1;
if (!match(pstate, TOKEN_EOS)) { if (!match(pstate, TOKEN_EOS)) {
@ -1186,6 +1233,10 @@ static void forLoop(CParseState *pstate) {
if (!match(pstate, TOKEN_RIGHT_PAREN)) { if (!match(pstate, TOKEN_RIGHT_PAREN)) {
int bodyJmp = writeJmp(pstate, OP_JMP); int bodyJmp = writeJmp(pstate, OP_JMP);
// replace stale loop state
endLoop(pstate);
startLoop(pstate);
int iteratorStart = getChunk(pstate)->count; int iteratorStart = getChunk(pstate)->count;
expression(pstate, 0, true); expression(pstate, 0, true);
consume(pstate, TOKEN_RIGHT_PAREN, "Expected ')' after iterator"); consume(pstate, TOKEN_RIGHT_PAREN, "Expected ')' after iterator");
@ -1207,9 +1258,44 @@ static void forLoop(CParseState *pstate) {
patchJmp(pstate, exitJmp); patchJmp(pstate, exitJmp);
} }
// patch all the breaks, and restore the previous loop state
endLoop(pstate);
pstate->compiler->loop = cachedLoop;
endScope(pstate); endScope(pstate);
} }
static void breakStatement(CParseState *pstate) {
if (pstate->compiler->loop.scope == -1) {
error(pstate, "'break' cannot be used inside of a loop body!");
return;
}
// pop active scoped locals in the loop scope
int savedLocals = pstate->compiler->localCount;
popLocals(pstate, pstate->compiler->loop.scope);
pstate->compiler->localCount = savedLocals;
// add break to loop
cosmoM_growarray(pstate->state, int, pstate->compiler->loop.breaks, pstate->compiler->loop.breakCount, pstate->compiler->loop.breakCapacity);
pstate->compiler->loop.breaks[pstate->compiler->loop.breakCount++] = writeJmp(pstate, OP_JMP);
}
static void continueStatement(CParseState *pstate) {
if (pstate->compiler->loop.scope == -1) {
error(pstate, "'continue' cannot be used inside of a loop body!");
return;
}
// pop active scoped locals in the loop scope
int savedLocals = pstate->compiler->localCount;
popLocals(pstate, pstate->compiler->loop.scope);
pstate->compiler->localCount = savedLocals;
// jump to the start of the loop
writeJmpBack(pstate, pstate->compiler->loop.startBytecode);
}
static void synchronize(CParseState *pstate) { static void synchronize(CParseState *pstate) {
pstate->panic = false; pstate->panic = false;
@ -1265,6 +1351,10 @@ static void expressionStatement(CParseState *pstate) {
functionDeclaration(pstate); functionDeclaration(pstate);
} else if (match(pstate, TOKEN_PROTO)) { } else if (match(pstate, TOKEN_PROTO)) {
_proto(pstate); _proto(pstate);
} else if (match(pstate, TOKEN_BREAK)) {
breakStatement(pstate);
} else if (match(pstate, TOKEN_CONTINUE)) {
continueStatement(pstate);
} else if (match(pstate, TOKEN_RETURN)) { } else if (match(pstate, TOKEN_RETURN)) {
returnStatement(pstate); returnStatement(pstate);
} else { } else {