From a408353c25d2fbc52a9129c9f9d651b12eed36ff Mon Sep 17 00:00:00 2001 From: CPunch Date: Thu, 24 Dec 2020 00:41:00 -0600 Subject: [PATCH] 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. --- examples/break.cosmo | 26 +++++++++++ src/clex.c | 2 + src/clex.h | 2 + src/cparse.c | 104 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 examples/break.cosmo diff --git a/examples/break.cosmo b/examples/break.cosmo new file mode 100644 index 0000000..2684f92 --- /dev/null +++ b/examples/break.cosmo @@ -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 \ No newline at end of file diff --git a/src/clex.c b/src/clex.c index 55ee9e8..1a0f018 100644 --- a/src/clex.c +++ b/src/clex.c @@ -5,6 +5,8 @@ CReservedWord reservedWords[] = { {TOKEN_AND, "and", 3}, + {TOKEN_BREAK, "break", 5}, + {TOKEN_CONTINUE, "continue", 8}, {TOKEN_DO, "do", 2}, {TOKEN_ELSE, "else", 4}, {TOKEN_ELSEIF, "elseif", 6}, diff --git a/src/clex.h b/src/clex.h index c082359..2195edc 100644 --- a/src/clex.h +++ b/src/clex.h @@ -44,6 +44,8 @@ typedef enum { // keywords & reserved words TOKEN_AND, + TOKEN_BREAK, + TOKEN_CONTINUE, TOKEN_DO, TOKEN_ELSE, TOKEN_ELSEIF, diff --git a/src/cparse.c b/src/cparse.c index e98106b..997bd49 100644 --- a/src/cparse.c +++ b/src/cparse.c @@ -20,6 +20,14 @@ typedef struct { bool isLocal; } 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 { FTYPE_FUNCTION, 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; typedef struct CCompilerState { - CObjFunction *function; - FunctionType type; - Local locals[256]; Upvalue upvalues[256]; + LoopState loop; + + CObjFunction *function; + FunctionType type; int localCount; int scopeDepth; int pushedValues; @@ -98,6 +107,8 @@ static void initCompilerState(CParseState* pstate, CCompilerState *ccstate, Func ccstate->function = cosmoO_newFunction(pstate->state); ccstate->function->module = pstate->module; + ccstate->loop.scope = -1; // there is no loop yet + if (type != FTYPE_SCRIPT) ccstate->function->name = cosmoO_copyString(pstate->state, pstate->previous.start, pstate->previous.length); else @@ -737,6 +748,8 @@ ParseRule ruleTable[] = { [TOKEN_TRUE] = {literal, NULL, PREC_NONE}, [TOKEN_FALSE] = {literal, NULL, PREC_NONE}, [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_ELSE] = {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) { + LoopState cachedLoop = pstate->compiler->loop; + startLoop(pstate); int jumpLocation = getChunk(pstate)->count; + + // get conditional expression(pstate, 1, true); consume(pstate, TOKEN_DO, "expected 'do' after conditional expression."); @@ -996,8 +1031,11 @@ static void whileStatement(CParseState *pstate) { beginScope(pstate); block(pstate); // parse until 'end' endScope(pstate); - writeJmpBack(pstate, jumpLocation); + + // patch all the breaks, and restore the previous loop state + endLoop(pstate); + pstate->compiler->loop = cachedLoop; 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 + // 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; // 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); // 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); + + // 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 // remove reserved local @@ -1170,8 +1214,11 @@ static void forLoop(CParseState *pstate) { consume(pstate, TOKEN_EOS, "Expected ';' after initializer!"); } + // start loop scope + LoopState cachedLoop = pstate->compiler->loop; + startLoop(pstate); int loopStart = getChunk(pstate)->count; - + // parse conditional int exitJmp = -1; if (!match(pstate, TOKEN_EOS)) { @@ -1186,6 +1233,10 @@ static void forLoop(CParseState *pstate) { if (!match(pstate, TOKEN_RIGHT_PAREN)) { int bodyJmp = writeJmp(pstate, OP_JMP); + // replace stale loop state + endLoop(pstate); + startLoop(pstate); + int iteratorStart = getChunk(pstate)->count; expression(pstate, 0, true); consume(pstate, TOKEN_RIGHT_PAREN, "Expected ')' after iterator"); @@ -1207,9 +1258,44 @@ static void forLoop(CParseState *pstate) { patchJmp(pstate, exitJmp); } + // patch all the breaks, and restore the previous loop state + endLoop(pstate); + pstate->compiler->loop = cachedLoop; + 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) { pstate->panic = false; @@ -1265,6 +1351,10 @@ static void expressionStatement(CParseState *pstate) { functionDeclaration(pstate); } else if (match(pstate, TOKEN_PROTO)) { _proto(pstate); + } else if (match(pstate, TOKEN_BREAK)) { + breakStatement(pstate); + } else if (match(pstate, TOKEN_CONTINUE)) { + continueStatement(pstate); } else if (match(pstate, TOKEN_RETURN)) { returnStatement(pstate); } else {