From c0d788656eb04f419e415bb3c31b4d03ce3b4675 Mon Sep 17 00:00:00 2001 From: cozis Date: Wed, 3 Nov 2021 01:25:18 +0000 Subject: [PATCH] added while and do-while loops --- samples/loop.noja | 16 ++++ src/common/executable.c | 1 + src/common/executable.h | 1 + src/compiler/ASTi.h | 22 ++++- src/compiler/compile.c | 86 +++++++++++++++++++ src/compiler/parse.c | 177 +++++++++++++++++++++++++++++++++++++++- src/runtime/runtime.c | 32 ++++++++ 7 files changed, 328 insertions(+), 7 deletions(-) create mode 100644 samples/loop.noja diff --git a/samples/loop.noja b/samples/loop.noja new file mode 100644 index 0000000..d59247d --- /dev/null +++ b/samples/loop.noja @@ -0,0 +1,16 @@ + +p = true; + +while p: + { + print('Hello!\n'); + p = false; + } + + + +do { + + print('Hello 2!\n'); + +} while true; \ No newline at end of file diff --git a/src/common/executable.c b/src/common/executable.c index c6a74a1..badc905 100644 --- a/src/common/executable.c +++ b/src/common/executable.c @@ -62,6 +62,7 @@ static const InstrInfo instr_table[] = { [OPCODE_RETURN] = {"RETURN", 0, NULL}, [OPCODE_JUMPIFNOTANDPOP] = {"JUMPIFNOTANDPOP", 1, (OperandType[]) {OPTP_INT}}, + [OPCODE_JUMPIFANDPOP] = {"JUMPIFANDPOP", 1, (OperandType[]) {OPTP_INT}}, [OPCODE_JUMP] = {"JUMP", 1, (OperandType[]) {OPTP_INT}}, }; diff --git a/src/common/executable.h b/src/common/executable.h index f9a69a5..711a7ee 100644 --- a/src/common/executable.h +++ b/src/common/executable.h @@ -40,6 +40,7 @@ typedef enum { OPCODE_PUSHNNE, OPCODE_PUSHFUN, OPCODE_RETURN, + OPCODE_JUMPIFANDPOP, OPCODE_JUMPIFNOTANDPOP, OPCODE_JUMP, } Opcode; diff --git a/src/compiler/ASTi.h b/src/compiler/ASTi.h index 837c2d4..a8f8fb7 100644 --- a/src/compiler/ASTi.h +++ b/src/compiler/ASTi.h @@ -11,6 +11,8 @@ typedef enum { NODE_RETURN, NODE_FUNC, NODE_ARG, + NODE_WHILE, + NODE_DOWHILE, } NodeKind; typedef enum { @@ -82,18 +84,30 @@ typedef struct { typedef struct { ExprNode base; - Node *func; - Node *argv; - int argc; + Node *func; + Node *argv; + int argc; } CallExprNode; typedef struct { - Node base; + Node base; Node *condition; Node *true_branch; Node *false_branch; } IfElseNode; +typedef struct { + Node base; + Node *condition; + Node *body; +} WhileNode; + +typedef struct { + Node base; + Node *body; + Node *condition; +} DoWhileNode; + typedef struct { Node base; Node *head; diff --git a/src/compiler/compile.c b/src/compiler/compile.c index 72e1334..bd3dbd1 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -225,6 +225,92 @@ static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error) return 1; } + case NODE_WHILE: + { + WhileNode *whl = (WhileNode*) node; + + /* + * start: + * + * JUMPIFNOTANDPOP end + * + * JUMP start + * end: + */ + + Promise *start_offset = Promise_New(ExeBuilder_GetAlloc(exeb), sizeof(long long int)); + Promise *end_offset = Promise_New(ExeBuilder_GetAlloc(exeb), sizeof(long long int)); + + if(start_offset == NULL || end_offset == NULL) + { + Error_Report(error, 1, "No memory"); + return 0; + } + + long long int temp = ExeBuilder_InstrCount(exeb); + Promise_Resolve(start_offset, &temp, sizeof(temp)); + + if(!emit_instr_for_node(exeb, whl->condition, error)) + return 0; + + Operand op = { .type = OPTP_PROMISE, .as_promise = end_offset }; + if(!ExeBuilder_Append(exeb, error, OPCODE_JUMPIFNOTANDPOP, &op, 1, whl->condition->offset, whl->condition->length)) + return 0; + + if(!emit_instr_for_node(exeb, whl->body, error)) + return 0; + + if(whl->body->kind == NODE_EXPR) + { + Operand op = (Operand) { .type = OPTP_INT, .as_int = 1 }; + if(!ExeBuilder_Append(exeb, error, OPCODE_POP, &op, 1, whl->body->offset, 0)) + return 0; + } + + op = (Operand) { .type = OPTP_PROMISE, .as_promise = start_offset }; + if(!ExeBuilder_Append(exeb, error, OPCODE_JUMP, &op, 1, node->offset, node->length)) + return 0; + + temp = ExeBuilder_InstrCount(exeb); + Promise_Resolve(end_offset, &temp, sizeof(temp)); + + Promise_Free(start_offset); + Promise_Free( end_offset); + return 1; + } + + case NODE_DOWHILE: + { + DoWhileNode *dowhl = (DoWhileNode*) node; + + /* + * start: + * + * + * JUMPIFANDPOP start + */ + + long long int start = ExeBuilder_InstrCount(exeb); + + if(!emit_instr_for_node(exeb, dowhl->body, error)) + return 0; + + if(dowhl->body->kind == NODE_EXPR) + { + Operand op = (Operand) { .type = OPTP_INT, .as_int = 1 }; + if(!ExeBuilder_Append(exeb, error, OPCODE_POP, &op, 1, dowhl->body->offset, 0)) + return 0; + } + + if(!emit_instr_for_node(exeb, dowhl->condition, error)) + return 0; + + Operand op = { .type = OPTP_INT, .as_int = start }; + if(!ExeBuilder_Append(exeb, error, OPCODE_JUMPIFANDPOP, &op, 1, dowhl->condition->offset, dowhl->condition->length)) + return 0; + return 1; + } + case NODE_COMP: { CompoundNode *comp = (CompoundNode*) node; diff --git a/src/compiler/parse.c b/src/compiler/parse.c index b3658ad..d68dd79 100644 --- a/src/compiler/parse.c +++ b/src/compiler/parse.c @@ -31,6 +31,8 @@ typedef enum { TKWTRUE, TKWFALSE, TKWRETURN, + TKWWHILE, + TKWDO, } TokenKind; typedef struct Token Token; @@ -54,6 +56,8 @@ static Node *parse_ifelse_statement(Context *ctx); static Node *parse_compound_statement(Context *ctx, TokenKind end); static Node *parse_function_definition(Context *ctx); static Node *parse_postfix_expression(Context *ctx); +static Node *parse_while_statement(Context *ctx); +static Node *parse_dowhile_statement(Context *ctx); static inline _Bool isoper(char c) { @@ -152,6 +156,14 @@ AST *parse(Source *src, BPAlloc *alloc, Error *error) { tok->kind = TKWRETURN; } + else if(matchstr(str + tok->offset, tok->length, "while")) + { + tok->kind = TKWWHILE; + } + else if(matchstr(str + tok->offset, tok->length, "do")) + { + tok->kind = TKWDO; + } #undef matchstr } @@ -437,6 +449,12 @@ static Node *parse_statement(Context *ctx) case TKWFUN: return parse_function_definition(ctx); + + case TKWWHILE: + return parse_while_statement(ctx); + + case TKWDO: + return parse_dowhile_statement(ctx); } Error_Report(ctx->error, 0, "Got token \"%.*s\" where the start of a statement was expected", @@ -449,10 +467,15 @@ static Node *parse_expression_statement(Context *ctx) assert(ctx != NULL); Node *expr = parse_expression(ctx); - if(expr == NULL) return NULL; + + if(expr == NULL) + return NULL; - // The final statement doesn't need a ';'. - if(done(ctx)) return expr; + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended right after an expression, where a ';' was expected"); + return NULL; + } if(current(ctx) != ';') { @@ -1377,4 +1400,152 @@ static Node *parse_function_definition(Context *ctx) } return (Node*) func; +} + +static Node *parse_while_statement(Context *ctx) +{ + assert(ctx != NULL); + + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended where a while statement was expected"); + return NULL; + } + + if(current(ctx) != TKWWHILE) + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where a while statement was expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + Token *while_token = current_token(ctx); + assert(while_token != NULL); + + next(ctx); // Consume the "while" keyword. + + Node *condition = parse_expression(ctx); + + if(condition == NULL) + return NULL; + + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended right after a while loop condition, where a ':' was expected"); + return NULL; + } + + if(current(ctx) != ':') + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" after a while loop condition, where a ':' was expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + next(ctx); // Skip the ':'. + + Node *body = parse_statement(ctx); + + if(body == NULL) + return NULL; + + WhileNode *whl; + { + whl = BPAlloc_Malloc(ctx->alloc, sizeof(WhileNode)); + + if(whl == NULL) + { + // ERROR: No memory. + Error_Report(ctx->error, 1, "No memory"); + return NULL; + } + + whl->base.kind = NODE_WHILE; + whl->base.next = NULL; + whl->base.offset = while_token->offset; + whl->base.length = ctx->token->offset + ctx->token->length - while_token->offset; + whl->condition = condition; + whl->body = body; + } + + return (Node*) whl; +} + +static Node *parse_dowhile_statement(Context *ctx) +{ + assert(ctx != NULL); + + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended where a do-while statement was expected"); + return NULL; + } + + if(current(ctx) != TKWDO) + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" where a do-while statement was expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + Token *do_token = current_token(ctx); + assert(do_token != NULL); + + next(ctx); // Consume the "do" keyword. + + Node *body = parse_statement(ctx); + + if(body == NULL) + return NULL; + + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended right after a do-while body, where the \"while\" keyword was expected"); + return NULL; + } + + if(current(ctx) != TKWWHILE) + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" after a do-while body, where the \"while\" keyword was expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + next(ctx); // Consume the "while" keyword. + + Node *condition = parse_expression(ctx); + + if(condition == NULL) + return NULL; + + if(done(ctx)) + { + Error_Report(ctx->error, 0, "Source ended right after a do-while condition, where a ';' was expected"); + return NULL; + } + + if(current(ctx) != ';') + { + Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" after a do-while conditnion, where a ';' was expected", ctx->token->length, ctx->src + ctx->token->offset); + return NULL; + } + + next(ctx); // Skip the ';'. + + DoWhileNode *dowhl; + { + dowhl = BPAlloc_Malloc(ctx->alloc, sizeof(DoWhileNode)); + + if(dowhl == NULL) + { + // ERROR: No memory. + Error_Report(ctx->error, 1, "No memory"); + return NULL; + } + + dowhl->base.kind = NODE_DOWHILE; + dowhl->base.next = NULL; + dowhl->base.offset = do_token->offset; + dowhl->base.length = ctx->token->offset + ctx->token->length - do_token->offset; + dowhl->condition = condition; + dowhl->body = body; + } + + return (Node*) dowhl; } \ No newline at end of file diff --git a/src/runtime/runtime.c b/src/runtime/runtime.c index 6bb0075..fe21d1d 100644 --- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -633,6 +633,38 @@ static _Bool step(Runtime *runtime, Error *error) runtime->frame->index = ops[0].as_int; return 1; + case OPCODE_JUMPIFANDPOP: + { + assert(opc == 1); + assert(ops[0].type == OPTP_INT); + + long long int target = ops[0].as_int; + + if(runtime->frame->used == 0) + { + Error_Report(error, 1, "Frame doesn't have enough items on the stack to execute JUMPIFNOTANDPOP"); + return 0; + } + + Object *top = Stack_Top(runtime->stack, 0); + + if(!Runtime_Pop(runtime, error, 1)) + return 0; + + assert(top != NULL); + + if(!Object_IsBool(top)) + { + Error_Report(error, 1, "JUMPIFNOTANDPOP expected a boolean on the stack"); + return 0; + } + + if(Object_ToBool(top, error)) // This can't fail because we know it's a bool. + runtime->frame->index = target; + + return 1; + } + case OPCODE_JUMPIFNOTANDPOP: { assert(opc == 1);