From 69a4e1a69239c4de6bcade6aac04205db10ec9c5 Mon Sep 17 00:00:00 2001
From: A404M <ahmadmahmoudiprogrammer@gmail.com>
Date: Fri, 16 May 2025 18:31:59 +0330
Subject: add continue

---
 code/main.felan         |   7 ++-
 src/compiler/ast-tree.c |  69 +++++++++++++++++++--
 src/compiler/ast-tree.h |   8 +++
 src/compiler/lexer.c    |   5 +-
 src/compiler/lexer.h    |   1 +
 src/compiler/parser.c   |   9 +++
 src/compiler/parser.h   |   1 +
 src/runner/runner.c     | 161 +++++++++++++++++++++++++++---------------------
 src/runner/runner.h     |   2 +-
 9 files changed, 185 insertions(+), 78 deletions(-)

diff --git a/code/main.felan b/code/main.felan
index 890e402..1a87625 100644
--- a/code/main.felan
+++ b/code/main.felan
@@ -3,8 +3,13 @@
 main :: () -> void {
   i := 0;
   while i < 10 {
+    if i == 7
+      break;
+    else if i % 2 == 0 {
+      i += 1;
+      continue;
+    }
     putc @cast(i,u8)+'0';
     i += 1;
-    break;
   }
 };
diff --git a/src/compiler/ast-tree.c b/src/compiler/ast-tree.c
index e0c9c71..3640236 100644
--- a/src/compiler/ast-tree.c
+++ b/src/compiler/ast-tree.c
@@ -151,6 +151,7 @@ const char *AST_TREE_TOKEN_STRINGS[] = {
     "AST_TREE_TOKEN_KEYWORD_PUTC",
     "AST_TREE_TOKEN_KEYWORD_RETURN",
     "AST_TREE_TOKEN_KEYWORD_BREAK",
+    "AST_TREE_TOKEN_KEYWORD_CONTINUE",
     "AST_TREE_TOKEN_KEYWORD_IF",
     "AST_TREE_TOKEN_KEYWORD_WHILE",
     "AST_TREE_TOKEN_KEYWORD_COMPTIME",
@@ -312,8 +313,12 @@ void astTreePrint(const AstTree *tree, int indent) {
   case AST_TREE_TOKEN_VALUE_NULL:
   case AST_TREE_TOKEN_VALUE_UNDEFINED:
   case AST_TREE_TOKEN_VARIABLE_DEFINE:
-  case AST_TREE_TOKEN_KEYWORD_BREAK:
     goto RETURN_SUCCESS;
+  case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE: {
+    AstTreeLoopControl *meatadata = tree->metadata;
+    printf("count=%d", meatadata->count);
+  }
   case AST_TREE_TOKEN_OPERATOR_LOGICAL_NOT:
   case AST_TREE_TOKEN_OPERATOR_PLUS:
   case AST_TREE_TOKEN_OPERATOR_MINUS: {
@@ -734,8 +739,13 @@ void astTreeDestroy(AstTree tree) {
   case AST_TREE_TOKEN_VALUE_UNDEFINED:
   case AST_TREE_TOKEN_VALUE_VOID:
   case AST_TREE_TOKEN_VARIABLE_DEFINE:
+    return;
   case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE: {
+    AstTreeLoopControl *meatadata = tree.metadata;
+    free(meatadata);
     return;
+  }
   case AST_TREE_TOKEN_VALUE_NAMESPACE: {
     AstTreeNamespace *metadata = tree.metadata;
     free(metadata);
@@ -1019,7 +1029,6 @@ AstTree *copyAstTreeBack(AstTree *tree, AstTreeVariables oldVariables[],
     return tree;
   case AST_TREE_TOKEN_VALUE_NULL:
   case AST_TREE_TOKEN_VALUE_UNDEFINED:
-  case AST_TREE_TOKEN_KEYWORD_BREAK:
   case AST_TREE_TOKEN_BUILTIN_CAST:
   case AST_TREE_TOKEN_BUILTIN_TYPE_OF:
   case AST_TREE_TOKEN_BUILTIN_IMPORT:
@@ -1042,7 +1051,18 @@ AstTree *copyAstTreeBack(AstTree *tree, AstTreeVariables oldVariables[],
                       copyAstTreeBack(tree->type, oldVariables, newVariables,
                                       variables_size, safetyCheck),
                       tree->str_begin, tree->str_end);
+  case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE: {
+    AstTreeLoopControl *metadata = tree->metadata;
+    AstTreeLoopControl *new_metadata = a404m_malloc(sizeof(*new_metadata));
 
+    new_metadata->count = metadata->count;
+
+    return newAstTree(tree->token, new_metadata,
+                      copyAstTreeBack(tree->type, oldVariables, newVariables,
+                                      variables_size, safetyCheck),
+                      tree->str_begin, tree->str_end);
+  }
   case AST_TREE_TOKEN_VALUE_NAMESPACE: {
     AstTreeNamespace *metadata = tree->metadata;
     AstTreeNamespace *newMetadata = a404m_malloc(sizeof(*newMetadata));
@@ -1984,6 +2004,7 @@ AstTreeRoot *makeAstRoot(const ParserNode *parsedRoot, char *filePath) {
       case PARSER_TOKEN_KEYWORD_PUTC:
       case PARSER_TOKEN_KEYWORD_RETURN:
       case PARSER_TOKEN_KEYWORD_BREAK:
+      case PARSER_TOKEN_KEYWORD_CONTINUE:
       case PARSER_TOKEN_CONSTANT:
       case PARSER_TOKEN_VARIABLE:
       case PARSER_TOKEN_SYMBOL_EOL:
@@ -2161,7 +2182,9 @@ AstTree *astTreeParse(const ParserNode *parserNode) {
   case PARSER_TOKEN_KEYWORD_UNDEFINED:
     return astTreeParseKeyword(parserNode, AST_TREE_TOKEN_VALUE_UNDEFINED);
   case PARSER_TOKEN_KEYWORD_BREAK:
-    return astTreeParseKeyword(parserNode, AST_TREE_TOKEN_KEYWORD_BREAK);
+    return astTreeParseLoopControl(parserNode, AST_TREE_TOKEN_KEYWORD_BREAK);
+  case PARSER_TOKEN_KEYWORD_CONTINUE:
+    return astTreeParseLoopControl(parserNode, AST_TREE_TOKEN_KEYWORD_CONTINUE);
   case PARSER_TOKEN_KEYWORD_PUTC:
     return astTreeParsePutc(parserNode);
   case PARSER_TOKEN_KEYWORD_RETURN:
@@ -2367,6 +2390,7 @@ AstTree *astTreeParseFunction(const ParserNode *parserNode) {
     case PARSER_TOKEN_KEYWORD_PUTC:
     case PARSER_TOKEN_KEYWORD_RETURN:
     case PARSER_TOKEN_KEYWORD_BREAK:
+    case PARSER_TOKEN_KEYWORD_CONTINUE:
     case PARSER_TOKEN_KEYWORD_COMPTIME:
     case PARSER_TOKEN_KEYWORD_STRUCT:
     case PARSER_TOKEN_CONSTANT:
@@ -2658,6 +2682,13 @@ AstTree *astTreeParseKeyword(const ParserNode *parserNode, AstTreeToken token) {
                     parserNode->str_end);
 }
 
+AstTree *astTreeParseLoopControl(const ParserNode *parserNode,
+                                 AstTreeToken token) {
+  AstTreeLoopControl *meatadata = a404m_malloc(sizeof(*meatadata));
+  return newAstTree(token, meatadata, NULL, parserNode->str_begin,
+                    parserNode->str_end);
+}
+
 AstTree *astTreeParsePutc(const ParserNode *parserNode) {
   ParserNodeSingleChildMetadata *node_metadata = parserNode->metadata;
 
@@ -2986,6 +3017,7 @@ AstTree *astTreeParseCurlyBracket(const ParserNode *parserNode) {
     case PARSER_TOKEN_KEYWORD_PUTC:
     case PARSER_TOKEN_KEYWORD_RETURN:
     case PARSER_TOKEN_KEYWORD_BREAK:
+    case PARSER_TOKEN_KEYWORD_CONTINUE:
     case PARSER_TOKEN_KEYWORD_COMPTIME:
     case PARSER_TOKEN_KEYWORD_STRUCT:
     case PARSER_TOKEN_CONSTANT:
@@ -3327,6 +3359,7 @@ bool isConst(AstTree *tree) {
   case AST_TREE_TOKEN_KEYWORD_PUTC:
   case AST_TREE_TOKEN_KEYWORD_RETURN:
   case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE:
   case AST_TREE_TOKEN_VARIABLE_DEFINE:
   case AST_TREE_TOKEN_OPERATOR_ASSIGN:
   case AST_TREE_TOKEN_OPERATOR_PLUS:
@@ -3552,6 +3585,7 @@ AstTree *makeTypeOf(AstTree *value) {
   case AST_TREE_TOKEN_KEYWORD_PUTC:
   case AST_TREE_TOKEN_KEYWORD_RETURN:
   case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE:
   case AST_TREE_TOKEN_KEYWORD_IF:
   case AST_TREE_TOKEN_KEYWORD_WHILE:
   case AST_TREE_TOKEN_SCOPE:
@@ -3629,6 +3663,7 @@ bool typeIsEqualBack(const AstTree *type0, const AstTree *type1) {
   case AST_TREE_TOKEN_KEYWORD_PUTC:
   case AST_TREE_TOKEN_KEYWORD_RETURN:
   case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE:
   case AST_TREE_TOKEN_KEYWORD_IF:
   case AST_TREE_TOKEN_KEYWORD_WHILE:
   case AST_TREE_TOKEN_KEYWORD_COMPTIME:
@@ -3853,6 +3888,7 @@ AstTree *getValue(AstTree *tree, bool copy) {
   case AST_TREE_TOKEN_SHAPE_SHIFTER_ELEMENT: {
     bool shouldRet = false;
     u32 breakCount = 0;
+    bool shouldContinue = false;
     AstTreeScope *scope = a404m_malloc(sizeof(*scope));
     scope->expressions = a404m_malloc(0);
     scope->expressions_size = 0;
@@ -3867,8 +3903,8 @@ AstTree *getValue(AstTree *tree, bool copy) {
         .str_end = NULL,
     };
 
-    AstTree *value =
-        runExpression(tree, scope, &shouldRet, false, true, &breakCount);
+    AstTree *value = runExpression(tree, scope, &shouldRet, false, true,
+                                   &breakCount, &shouldContinue);
 
     if (!copy) {
       astTreeDelete(tree);
@@ -3893,6 +3929,7 @@ AstTree *getValue(AstTree *tree, bool copy) {
   case AST_TREE_TOKEN_KEYWORD_PUTC:
   case AST_TREE_TOKEN_KEYWORD_RETURN:
   case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE:
   case AST_TREE_TOKEN_VARIABLE_DEFINE:
   case AST_TREE_TOKEN_NONE:
   }
@@ -3932,6 +3969,7 @@ bool isIntType(AstTree *type) {
   case AST_TREE_TOKEN_KEYWORD_PUTC:
   case AST_TREE_TOKEN_KEYWORD_RETURN:
   case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE:
   case AST_TREE_TOKEN_KEYWORD_IF:
   case AST_TREE_TOKEN_KEYWORD_WHILE:
   case AST_TREE_TOKEN_KEYWORD_COMPTIME:
@@ -4060,6 +4098,7 @@ bool isEqual(AstTree *left, AstTree *right) {
   case AST_TREE_TOKEN_KEYWORD_PUTC:
   case AST_TREE_TOKEN_KEYWORD_RETURN:
   case AST_TREE_TOKEN_KEYWORD_BREAK:
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE:
   case AST_TREE_TOKEN_KEYWORD_IF:
   case AST_TREE_TOKEN_KEYWORD_WHILE:
   case AST_TREE_TOKEN_KEYWORD_COMPTIME:
@@ -4304,6 +4343,8 @@ bool setAllTypes(AstTree *tree, AstTreeSetTypesHelper helper,
     return setTypesReturn(tree, helper, function);
   case AST_TREE_TOKEN_KEYWORD_BREAK:
     return setTypesBreak(tree, helper);
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE:
+    return setTypesContinue(tree, helper);
   case AST_TREE_TOKEN_TYPE_FUNCTION:
     return setTypesTypeFunction(tree, helper);
   case AST_TREE_TOKEN_FUNCTION_CALL:
@@ -4755,6 +4796,23 @@ bool setTypesBreak(AstTree *tree, AstTreeSetTypesHelper helper) {
     return false;
   }
 
+  AstTreeLoopControl *meatadata = tree->metadata;
+  meatadata->count = 1;
+
+  tree->type = &AST_TREE_VOID_TYPE;
+  return true;
+}
+
+bool setTypesContinue(AstTree *tree, AstTreeSetTypesHelper helper) {
+  if (helper.loops_size == 0) {
+    printError(tree->str_begin, tree->str_end,
+               "`continue` can't be here, it needs to be inside a loop");
+    return false;
+  }
+
+  AstTreeLoopControl *meatadata = tree->metadata;
+  meatadata->count = 1;
+
   tree->type = &AST_TREE_VOID_TYPE;
   return true;
 }
@@ -5058,7 +5116,6 @@ bool setTypesVariable(AstTree *tree, AstTreeSetTypesHelper helper,
   AstTreeVariable *variable = setTypesFindVariable(
       tree->str_begin, tree->str_end, helper, functionCall);
   if (variable == NULL) {
-    printError(tree->str_begin, tree->str_end, "No candidate found");
     goto RETURN_ERROR;
   }
 
diff --git a/src/compiler/ast-tree.h b/src/compiler/ast-tree.h
index fd1f123..1330efa 100644
--- a/src/compiler/ast-tree.h
+++ b/src/compiler/ast-tree.h
@@ -28,6 +28,7 @@ typedef enum AstTreeToken {
   AST_TREE_TOKEN_KEYWORD_PUTC,
   AST_TREE_TOKEN_KEYWORD_RETURN,
   AST_TREE_TOKEN_KEYWORD_BREAK,
+  AST_TREE_TOKEN_KEYWORD_CONTINUE,
   AST_TREE_TOKEN_KEYWORD_IF,
   AST_TREE_TOKEN_KEYWORD_WHILE,
   AST_TREE_TOKEN_KEYWORD_COMPTIME,
@@ -298,6 +299,10 @@ typedef struct AstTreeShapeShifterElement {
   size_t index;
 } AstTreeShapeShifterElement;
 
+typedef struct AstTreeLoopControl {
+  u32 count;
+} AstTreeLoopControl;
+
 #ifdef PRINT_COMPILE_TREE
 void astTreePrint(const AstTree *tree, int indent);
 void astTreeVariablePrint(const AstTreeVariable *variable, int indent);
@@ -358,6 +363,8 @@ AstTree *astTreeParseValue(const ParserNode *parserNode, AstTreeToken token,
                            size_t metadata_size, AstTree *type);
 AstTree *astTreeParseString(const ParserNode *parserNode);
 AstTree *astTreeParseKeyword(const ParserNode *parserNode, AstTreeToken token);
+AstTree *astTreeParseLoopControl(const ParserNode *parserNode,
+                                 AstTreeToken token);
 AstTree *astTreeParsePutc(const ParserNode *parserNode);
 AstTree *astTreeParseReturn(const ParserNode *parserNode);
 AstTree *astTreeParseBinaryOperator(const ParserNode *parserNode,
@@ -411,6 +418,7 @@ bool setTypesPutc(AstTree *tree, AstTreeSetTypesHelper helper);
 bool setTypesReturn(AstTree *tree, AstTreeSetTypesHelper helper,
                     AstTreeFunction *function);
 bool setTypesBreak(AstTree *tree, AstTreeSetTypesHelper helper);
+bool setTypesContinue(AstTree *tree, AstTreeSetTypesHelper helper);
 bool setTypesTypeFunction(AstTree *tree, AstTreeSetTypesHelper helper);
 bool setTypesFunctionCall(AstTree *tree, AstTreeSetTypesHelper helper);
 bool setTypesVariable(AstTree *tree, AstTreeSetTypesHelper helper,
diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c
index 517e650..6804080 100644
--- a/src/compiler/lexer.c
+++ b/src/compiler/lexer.c
@@ -100,6 +100,7 @@ const char *LEXER_TOKEN_STRINGS[] = {
     "LEXER_TOKEN_KEYWORD_RETURN",
     "LEXER_TOKEN_KEYWORD_PUTC",
     "LEXER_TOKEN_KEYWORD_BREAK",
+    "LEXER_TOKEN_KEYWORD_CONTINUE",
     "LEXER_TOKEN_KEYWORD_COMPTIME",
 
     "LEXER_TOKEN_SYMBOL_EOL",
@@ -171,7 +172,7 @@ static const char *LEXER_KEYWORD_STRINGS[] = {
     "f32",           "f64",    "f128",      "bool", "putc",  "return",
     "true",          "false",  "if",        "else", "while", "comptime",
     "null",          "struct", "undefined", "code", "lazy",  "namespace",
-    "shape_shifter", "break",
+    "shape_shifter", "break",  "continue",
 };
 static const LexerToken LEXER_KEYWORD_TOKENS[] = {
     LEXER_TOKEN_KEYWORD_TYPE,
@@ -207,6 +208,7 @@ static const LexerToken LEXER_KEYWORD_TOKENS[] = {
     LEXER_TOKEN_KEYWORD_NAMESPACE,
     LEXER_TOKEN_KEYWORD_SHAPE_SHIFTER,
     LEXER_TOKEN_KEYWORD_BREAK,
+    LEXER_TOKEN_KEYWORD_CONTINUE,
 };
 static const size_t LEXER_KEYWORD_SIZE =
     sizeof(LEXER_KEYWORD_TOKENS) / sizeof(*LEXER_KEYWORD_TOKENS);
@@ -522,6 +524,7 @@ lexerPushClear(LexerNodeArray *array, size_t *array_size, char const *iter,
   case LEXER_TOKEN_SYMBOL_OPEN_BRACKET:
   case LEXER_TOKEN_KEYWORD_SHAPE_SHIFTER:
   case LEXER_TOKEN_KEYWORD_BREAK:
+  case LEXER_TOKEN_KEYWORD_CONTINUE:
     if (*array_size == array->size) {
       *array_size += 1 + *array_size / 2;
       array->data =
diff --git a/src/compiler/lexer.h b/src/compiler/lexer.h
index 641e7c8..0cc19cd 100644
--- a/src/compiler/lexer.h
+++ b/src/compiler/lexer.h
@@ -112,6 +112,7 @@ typedef enum LexerToken {
   LEXER_TOKEN_ORDER11 = LEXER_TOKEN_KEYWORD_RETURN,
   LEXER_TOKEN_KEYWORD_PUTC,
   LEXER_TOKEN_KEYWORD_BREAK,
+  LEXER_TOKEN_KEYWORD_CONTINUE,
   LEXER_TOKEN_KEYWORD_COMPTIME,
 
   LEXER_TOKEN_SYMBOL_EOL,
diff --git a/src/compiler/parser.c b/src/compiler/parser.c
index 4193fd3..b7a6e46 100644
--- a/src/compiler/parser.c
+++ b/src/compiler/parser.c
@@ -61,6 +61,7 @@ const char *PARSER_TOKEN_STRINGS[] = {
 
     "PARSER_TOKEN_KEYWORD_PUTC",
     "PARSER_TOKEN_KEYWORD_BREAK",
+    "PARSER_TOKEN_KEYWORD_CONTINUE",
     "PARSER_TOKEN_KEYWORD_RETURN",
     "PARSER_TOKEN_KEYWORD_IF",
     "PARSER_TOKEN_KEYWORD_WHILE",
@@ -282,6 +283,7 @@ void parserNodePrint(const ParserNode *node, int indent) {
   case PARSER_TOKEN_KEYWORD_NULL:
   case PARSER_TOKEN_KEYWORD_UNDEFINED:
   case PARSER_TOKEN_KEYWORD_BREAK:
+  case PARSER_TOKEN_KEYWORD_CONTINUE:
     goto RETURN_SUCCESS;
   case PARSER_TOKEN_VALUE_INT: {
     ParserNodeIntMetadata *metadata = node->metadata;
@@ -586,6 +588,7 @@ void parserNodeDelete(ParserNode *node) {
   case PARSER_TOKEN_KEYWORD_NULL:
   case PARSER_TOKEN_KEYWORD_UNDEFINED:
   case PARSER_TOKEN_KEYWORD_BREAK:
+  case PARSER_TOKEN_KEYWORD_CONTINUE:
     goto RETURN_SUCCESS;
   case PARSER_TOKEN_VALUE_BOOL: {
     ParserNodeBoolMetadata *metadata = node->metadata;
@@ -929,6 +932,8 @@ ParserNode *parseNode(LexerNode *node, LexerNode *begin, LexerNode *end,
     return parserNoMetadata(node, parent, PARSER_TOKEN_KEYWORD_UNDEFINED);
   case LEXER_TOKEN_KEYWORD_BREAK:
     return parserNoMetadata(node, parent, PARSER_TOKEN_KEYWORD_BREAK);
+  case LEXER_TOKEN_KEYWORD_CONTINUE:
+    return parserNoMetadata(node, parent, PARSER_TOKEN_KEYWORD_CONTINUE);
   case LEXER_TOKEN_KEYWORD_PUTC:
     return parserPutc(node, end, parent);
   case LEXER_TOKEN_KEYWORD_RETURN:
@@ -1720,6 +1725,7 @@ ParserNode *parserFunction(LexerNode *node, LexerNode *begin, LexerNode *end,
       case PARSER_TOKEN_KEYWORD_NULL:
       case PARSER_TOKEN_KEYWORD_UNDEFINED:
       case PARSER_TOKEN_KEYWORD_BREAK:
+      case PARSER_TOKEN_KEYWORD_CONTINUE:
       case PARSER_TOKEN_KEYWORD_PUTC:
       case PARSER_TOKEN_KEYWORD_RETURN:
       case PARSER_TOKEN_KEYWORD_STRUCT:
@@ -2258,6 +2264,7 @@ bool isExpression(ParserNode *node) {
   case PARSER_TOKEN_KEYWORD_NULL:
   case PARSER_TOKEN_KEYWORD_UNDEFINED:
   case PARSER_TOKEN_KEYWORD_BREAK:
+  case PARSER_TOKEN_KEYWORD_CONTINUE:
   case PARSER_TOKEN_KEYWORD_STRUCT:
     return true;
   case PARSER_TOKEN_ROOT:
@@ -2340,6 +2347,7 @@ bool isType(ParserNode *node) {
   case PARSER_TOKEN_KEYWORD_PUTC:
   case PARSER_TOKEN_KEYWORD_RETURN:
   case PARSER_TOKEN_KEYWORD_BREAK:
+  case PARSER_TOKEN_KEYWORD_CONTINUE:
   case PARSER_TOKEN_OPERATOR_ASSIGN:
   case PARSER_TOKEN_OPERATOR_SUM_ASSIGN:
   case PARSER_TOKEN_OPERATOR_SUB_ASSIGN:
@@ -2463,6 +2471,7 @@ bool isValue(ParserNode *node) {
   case PARSER_TOKEN_KEYWORD_PUTC:
   case PARSER_TOKEN_KEYWORD_RETURN:
   case PARSER_TOKEN_KEYWORD_BREAK:
+  case PARSER_TOKEN_KEYWORD_CONTINUE:
   case PARSER_TOKEN_KEYWORD_WHILE:
     return false;
   case PARSER_TOKEN_NONE:
diff --git a/src/compiler/parser.h b/src/compiler/parser.h
index 4a1b5ac..a322747 100644
--- a/src/compiler/parser.h
+++ b/src/compiler/parser.h
@@ -58,6 +58,7 @@ typedef enum ParserToken {
 
   PARSER_TOKEN_KEYWORD_PUTC,
   PARSER_TOKEN_KEYWORD_BREAK,
+  PARSER_TOKEN_KEYWORD_CONTINUE,
   PARSER_TOKEN_KEYWORD_RETURN,
   PARSER_TOKEN_KEYWORD_IF,
   PARSER_TOKEN_KEYWORD_WHILE,
diff --git a/src/runner/runner.c b/src/runner/runner.c
index 847c6b2..63c8435 100644
--- a/src/runner/runner.c
+++ b/src/runner/runner.c
@@ -95,21 +95,23 @@ AstTree *runAstTreeFunction(AstTree *tree, AstTreeFunctionCallParam *arguments,
 
   bool shouldRet = false;
   u32 breakCount = 0;
+  bool shouldContinue = false;
 
   for (size_t i = 0; i < arguments_size; ++i) {
     AstTreeFunctionCallParam param = arguments[i];
     AstTreeVariable *arg = function->arguments.data[i];
-    AstTree *value = runExpression(param.value, &function->scope, &shouldRet,
-                                   false, isComptime, &breakCount);
+    AstTree *value =
+        runExpression(param.value, &function->scope, &shouldRet, false,
+                      isComptime, &breakCount, &shouldContinue);
     runnerVariableSetValueWihtoutConstCheck(arg, value);
   }
 
   shouldRet = false;
 
   for (size_t i = 0; i < function->scope.expressions_size; ++i) {
-    AstTree *ret =
-        runExpression(function->scope.expressions[i], &function->scope,
-                      &shouldRet, false, isComptime, &breakCount);
+    AstTree *ret = runExpression(function->scope.expressions[i],
+                                 &function->scope, &shouldRet, false,
+                                 isComptime, &breakCount, &shouldContinue);
     if (shouldRet) {
       return ret;
     } else {
@@ -130,11 +132,12 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
 
   bool shouldRet = false;
   u32 breakCount = 0;
+  bool shouldContinue = false;
 
   for (size_t i = 0; i < arguments_size; ++i) {
     AstTreeFunctionCallParam param = arguments[i];
     args.data[i] = runExpression(param.value, scope, &shouldRet, false,
-                                 isComptime, &breakCount);
+                                 isComptime, &breakCount, &shouldContinue);
   }
 
   if (shouldRet) {
@@ -386,8 +389,8 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_NEG: {
     AstTree *value = args.data[0];
     bool shouldRet = false;
-    ret =
-        runExpression(value, scope, &shouldRet, false, isComptime, &breakCount);
+    ret = runExpression(value, scope, &shouldRet, false, isComptime,
+                        &breakCount, &shouldContinue);
     switch (value->type->token) {
     case AST_TREE_TOKEN_TYPE_I8:
       *(i8 *)ret->metadata = -*(i8 *)ret->metadata;
@@ -435,9 +438,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_ADD: {
     bool shouldRet = false;
     ret = runExpression(args.data[0], scope, &shouldRet, false, isComptime,
-                        &breakCount);
+                        &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     switch (ret->type->token) {
     case AST_TREE_TOKEN_TYPE_I8:
@@ -488,9 +491,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_SUB: {
     bool shouldRet = false;
     ret = runExpression(args.data[0], scope, &shouldRet, false, isComptime,
-                        &breakCount);
+                        &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     switch (ret->type->token) {
     case AST_TREE_TOKEN_TYPE_I8:
@@ -541,9 +544,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_MUL: {
     bool shouldRet = false;
     ret = runExpression(args.data[0], scope, &shouldRet, false, isComptime,
-                        &breakCount);
+                        &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     switch (ret->type->token) {
     case AST_TREE_TOKEN_TYPE_I8:
@@ -594,9 +597,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_DIV: {
     bool shouldRet = false;
     ret = runExpression(args.data[0], scope, &shouldRet, false, isComptime,
-                        &breakCount);
+                        &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     switch (ret->type->token) {
     case AST_TREE_TOKEN_TYPE_I8:
@@ -647,9 +650,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_MOD: {
     bool shouldRet = false;
     ret = runExpression(args.data[0], scope, &shouldRet, false, isComptime,
-                        &breakCount);
+                        &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     switch (ret->type->token) {
     case AST_TREE_TOKEN_TYPE_I8:
@@ -685,9 +688,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_EQUAL: {
     bool shouldRet = false;
     AstTree *left = runExpression(args.data[0], scope, &shouldRet, false,
-                                  isComptime, &breakCount);
+                                  isComptime, &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     ret =
         newAstTree(AST_TREE_TOKEN_VALUE_BOOL, a404m_malloc(sizeof(AstTreeBool)),
@@ -761,9 +764,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_NOT_EQUAL: {
     bool shouldRet = false;
     AstTree *left = runExpression(args.data[0], scope, &shouldRet, false,
-                                  isComptime, &breakCount);
+                                  isComptime, &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     ret =
         newAstTree(AST_TREE_TOKEN_VALUE_BOOL, a404m_malloc(sizeof(AstTreeBool)),
@@ -837,9 +840,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_GREATER: {
     bool shouldRet = false;
     AstTree *left = runExpression(args.data[0], scope, &shouldRet, false,
-                                  isComptime, &breakCount);
+                                  isComptime, &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     ret =
         newAstTree(AST_TREE_TOKEN_VALUE_BOOL, a404m_malloc(sizeof(AstTreeBool)),
@@ -906,9 +909,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_SMALLER: {
     bool shouldRet = false;
     AstTree *left = runExpression(args.data[0], scope, &shouldRet, false,
-                                  isComptime, &breakCount);
+                                  isComptime, &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     ret =
         newAstTree(AST_TREE_TOKEN_VALUE_BOOL, a404m_malloc(sizeof(AstTreeBool)),
@@ -975,9 +978,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_GREATER_OR_EQUAL: {
     bool shouldRet = false;
     AstTree *left = runExpression(args.data[0], scope, &shouldRet, false,
-                                  isComptime, &breakCount);
+                                  isComptime, &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     ret =
         newAstTree(AST_TREE_TOKEN_VALUE_BOOL, a404m_malloc(sizeof(AstTreeBool)),
@@ -1048,9 +1051,9 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
   case AST_TREE_TOKEN_BUILTIN_SMALLER_OR_EQUAL: {
     bool shouldRet = false;
     AstTree *left = runExpression(args.data[0], scope, &shouldRet, false,
-                                  isComptime, &breakCount);
+                                  isComptime, &breakCount, &shouldContinue);
     AstTree *right = runExpression(args.data[1], scope, &shouldRet, false,
-                                   isComptime, &breakCount);
+                                   isComptime, &breakCount, &shouldContinue);
 
     ret =
         newAstTree(AST_TREE_TOKEN_VALUE_BOOL, a404m_malloc(sizeof(AstTreeBool)),
@@ -1133,20 +1136,22 @@ RETURN:
 }
 
 AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
-                       bool isLeft, bool isComptime, u32 *breakCount) {
+                       bool isLeft, bool isComptime, u32 *breakCount,
+                       bool *shouldContinue) {
   switch (expr->token) {
   case AST_TREE_TOKEN_KEYWORD_PUTC: {
     AstTreeSingleChild *metadata = expr->metadata;
     AstTree *tree = runExpression(metadata, scope, shouldRet, false, isComptime,
-                                  breakCount);
+                                  breakCount, shouldContinue);
     putchar((u8) * (AstTreeInt *)tree->metadata);
     astTreeDelete(tree);
     return &AST_TREE_VOID_VALUE;
   }
   case AST_TREE_TOKEN_FUNCTION_CALL: {
     AstTreeFunctionCall *metadata = expr->metadata;
-    AstTree *function = runExpression(metadata->function, scope, shouldRet,
-                                      false, isComptime, breakCount);
+    AstTree *function =
+        runExpression(metadata->function, scope, shouldRet, false, isComptime,
+                      breakCount, shouldContinue);
     AstTree *result;
     if (function->token == AST_TREE_TOKEN_FUNCTION) {
       result = runAstTreeFunction(function, metadata->parameters,
@@ -1177,14 +1182,14 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
   case AST_TREE_TOKEN_OPERATOR_ASSIGN: {
     AstTreeInfix *metadata = expr->metadata;
     AstTree *l = runExpression(metadata->left, scope, shouldRet, true,
-                               isComptime, breakCount);
+                               isComptime, breakCount, shouldContinue);
     if (l->token != AST_TREE_TOKEN_VARIABLE) {
       UNREACHABLE;
     }
     AstTreeVariable *left = l->metadata;
-    runnerVariableSetValue(left,
-                           runExpression(metadata->right, scope, shouldRet,
-                                         false, isComptime, breakCount));
+    runnerVariableSetValue(left, runExpression(metadata->right, scope,
+                                               shouldRet, false, isComptime,
+                                               breakCount, shouldContinue));
     astTreeDelete(l);
     return copyAstTree(left->value);
   }
@@ -1193,7 +1198,7 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
     *shouldRet = true;
     if (metadata->value != NULL) {
       return runExpression(metadata->value, scope, shouldRet, false, isComptime,
-                           breakCount);
+                           breakCount, shouldContinue);
     } else {
       return &AST_TREE_VOID_VALUE;
     }
@@ -1205,22 +1210,23 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
       value = copyAstTree(variable->initValue);
     } else {
       value = runExpression(variable->initValue, scope, shouldRet, false,
-                            isComptime, breakCount);
+                            isComptime, breakCount, shouldContinue);
     }
     runnerVariableSetValue(variable, value);
     return &AST_TREE_VOID_VALUE;
   }
   case AST_TREE_TOKEN_KEYWORD_IF: {
     AstTreeIf *metadata = expr->metadata;
-    AstTree *condition = runExpression(metadata->condition, scope, shouldRet,
-                                       false, isComptime, breakCount);
+    AstTree *condition =
+        runExpression(metadata->condition, scope, shouldRet, false, isComptime,
+                      breakCount, shouldContinue);
     AstTree *ret;
     if (*(AstTreeBool *)condition->metadata) {
       ret = runExpression(metadata->ifBody, scope, shouldRet, isLeft,
-                          isComptime, breakCount);
+                          isComptime, breakCount, shouldContinue);
     } else if (metadata->elseBody != NULL) {
       ret = runExpression(metadata->elseBody, scope, shouldRet, isLeft,
-                          isComptime, breakCount);
+                          isComptime, breakCount, shouldContinue);
     } else {
       ret = &AST_TREE_VOID_VALUE;
     }
@@ -1231,8 +1237,9 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
     AstTreeWhile *metadata = expr->metadata;
     AstTree *ret = &AST_TREE_VOID_VALUE;
     while (!*shouldRet) {
-      AstTree *tree = runExpression(metadata->condition, scope, shouldRet,
-                                    false, isComptime, breakCount);
+      AstTree *tree =
+          runExpression(metadata->condition, scope, shouldRet, false,
+                        isComptime, breakCount, shouldContinue);
       bool conti = *(AstTreeBool *)tree->metadata;
       astTreeDelete(tree);
       if (!conti) {
@@ -1240,8 +1247,11 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
       }
       astTreeDelete(ret);
       ret = runExpression(metadata->body, scope, shouldRet, isLeft, isComptime,
-                          breakCount);
-      if (*breakCount != 0) {
+                          breakCount, shouldContinue);
+      if (*breakCount == 1 && *shouldContinue) {
+        *breakCount -= 1;
+        *shouldContinue = false;
+      } else if (*breakCount != 0) {
         *breakCount -= 1;
         break;
       }
@@ -1251,7 +1261,7 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
   case AST_TREE_TOKEN_KEYWORD_COMPTIME: {
     AstTreeSingleChild *operand = expr->metadata;
     return runExpression((AstTree *)operand, scope, shouldRet, isLeft,
-                         isComptime, breakCount);
+                         isComptime, breakCount, shouldContinue);
   }
   case AST_TREE_TOKEN_SCOPE: {
     AstTreeScope *metadata = expr->metadata;
@@ -1261,7 +1271,10 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
       astTreeDelete(ret);
       ret = runExpression(metadata->expressions[i], scope, shouldRet,
                           i == metadata->expressions_size - 1 && isLeft,
-                          isComptime, breakCount);
+                          isComptime, breakCount, shouldContinue);
+      if (*breakCount > 0) {
+        break;
+      }
     }
     return ret;
   }
@@ -1269,8 +1282,9 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
   case AST_TREE_TOKEN_OPERATOR_MINUS:
   case AST_TREE_TOKEN_OPERATOR_PLUS: {
     AstTreeUnary *metadata = expr->metadata;
-    AstTree *function = runExpression(metadata->function->value, scope,
-                                      shouldRet, false, isComptime, breakCount);
+    AstTree *function =
+        runExpression(metadata->function->value, scope, shouldRet, false,
+                      isComptime, breakCount, shouldContinue);
     AstTreeFunctionCallParam arguments[] = {
         (AstTreeFunctionCallParam){
             .nameBegin = NULL,
@@ -1296,8 +1310,9 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
   case AST_TREE_TOKEN_OPERATOR_LOGICAL_AND:
   case AST_TREE_TOKEN_OPERATOR_LOGICAL_OR: {
     AstTreeInfix *metadata = expr->metadata;
-    AstTree *function = runExpression(metadata->function->value, scope,
-                                      shouldRet, false, isComptime, breakCount);
+    AstTree *function =
+        runExpression(metadata->function->value, scope, shouldRet, false,
+                      isComptime, breakCount, shouldContinue);
 
     AstTreeFunctionCallParam arguments[] = {
         (AstTreeFunctionCallParam){
@@ -1382,7 +1397,7 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
   case AST_TREE_TOKEN_OPERATOR_DEREFERENCE: {
     AstTreeSingleChild *metadata = expr->metadata;
     AstTree *operand = runExpression(metadata, scope, shouldRet, false,
-                                     isComptime, breakCount);
+                                     isComptime, breakCount, shouldContinue);
     if (operand->token != AST_TREE_TOKEN_VARIABLE) {
       printLog("%s", AST_TREE_TOKEN_STRINGS[operand->token]);
       UNREACHABLE;
@@ -1407,7 +1422,7 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
       }
       if (variable->isLazy) {
         return runExpression(variable->value, scope, shouldRet, false,
-                             isComptime, breakCount);
+                             isComptime, breakCount, shouldContinue);
       } else {
         return copyAstTree(variable->value);
       }
@@ -1416,7 +1431,7 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
   case AST_TREE_TOKEN_OPERATOR_ACCESS: {
     AstTreeAccess *metadata = expr->metadata;
     AstTree *tree = runExpression(metadata->object, scope, shouldRet, true,
-                                  isComptime, breakCount);
+                                  isComptime, breakCount, shouldContinue);
     if (tree->token != AST_TREE_TOKEN_VARIABLE) {
       UNREACHABLE;
     }
@@ -1430,9 +1445,9 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
         if (array_metadata->parameters.size == 0) {
           UNREACHABLE;
         } else {
-          AstTree *sizeTree =
-              runExpression(array_metadata->parameters.data[0], scope,
-                            shouldRet, false, isComptime, breakCount);
+          AstTree *sizeTree = runExpression(array_metadata->parameters.data[0],
+                                            scope, shouldRet, false, isComptime,
+                                            breakCount, shouldContinue);
           if (sizeTree->token != AST_TREE_TOKEN_VALUE_INT) {
             UNREACHABLE;
           } else {
@@ -1489,7 +1504,7 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
       AstTreeVariable *member = metadata->variables.data[i];
       AstTree *type = member->type;
       member->type = runExpression(member->type, scope, shouldRet, isLeft,
-                                   isComptime, breakCount);
+                                   isComptime, breakCount, shouldContinue);
       if (type != member->type) {
         astTreeDelete(type);
       }
@@ -1498,8 +1513,9 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
   }
   case AST_TREE_TOKEN_OPERATOR_POINTER: {
     AstTreeSingleChild *metadata = expr->metadata;
-    AstTreeSingleChild *newMetadata = runExpression(
-        metadata, scope, shouldRet, isLeft, isComptime, breakCount);
+    AstTreeSingleChild *newMetadata =
+        runExpression(metadata, scope, shouldRet, isLeft, isComptime,
+                      breakCount, shouldContinue);
 
     return newAstTree(AST_TREE_TOKEN_OPERATOR_POINTER, newMetadata,
                       copyAstTree(expr->type), expr->str_begin, expr->str_end);
@@ -1507,7 +1523,7 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
   case AST_TREE_TOKEN_OPERATOR_ARRAY_ACCESS: {
     AstTreeBracket *metadata = expr->metadata;
     AstTree *operand = runExpression(metadata->operand, scope, shouldRet, true,
-                                     isComptime, breakCount);
+                                     isComptime, breakCount, shouldContinue);
 
     if (operand->token != AST_TREE_TOKEN_VARIABLE) {
       UNREACHABLE;
@@ -1517,7 +1533,7 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
 
     AstTree *array_indexNode =
         runExpression(metadata->parameters.data[0], scope, shouldRet, false,
-                      isComptime, breakCount);
+                      isComptime, breakCount, shouldContinue);
 
     if (array_indexNode->token != AST_TREE_TOKEN_VALUE_INT) {
       UNREACHABLE;
@@ -1533,9 +1549,9 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
       if (array_type_metadata->parameters.size != 1) {
         UNREACHABLE;
       }
-      AstTree *arraySize_tree =
-          runExpression(array_type_metadata->parameters.data[0], scope,
-                        shouldRet, false, isComptime, breakCount);
+      AstTree *arraySize_tree = runExpression(
+          array_type_metadata->parameters.data[0], scope, shouldRet, false,
+          isComptime, breakCount, shouldContinue);
       if (arraySize_tree->token != AST_TREE_TOKEN_VALUE_INT) {
         UNREACHABLE;
       }
@@ -1601,7 +1617,14 @@ AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
                       copyAstTree(expr->type), expr->str_begin, expr->str_end);
   }
   case AST_TREE_TOKEN_KEYWORD_BREAK: {
-    *breakCount += 1;
+    AstTreeLoopControl *metadata = expr->metadata;
+    *breakCount = metadata->count;
+    return copyAstTree(&AST_TREE_VOID_VALUE);
+  }
+  case AST_TREE_TOKEN_KEYWORD_CONTINUE: {
+    AstTreeLoopControl *metadata = expr->metadata;
+    *breakCount = metadata->count;
+    *shouldContinue = true;
     return copyAstTree(&AST_TREE_VOID_VALUE);
   }
   case AST_TREE_TOKEN_NONE:
diff --git a/src/runner/runner.h b/src/runner/runner.h
index df8ff21..5a12db3 100644
--- a/src/runner/runner.h
+++ b/src/runner/runner.h
@@ -17,4 +17,4 @@ AstTree *runAstTreeBuiltin(AstTree *tree, AstTreeScope *scope,
                            size_t arguments_size, bool isComptime);
 
 AstTree *runExpression(AstTree *expr, AstTreeScope *scope, bool *shouldRet,
-                       bool isLeft, bool isComptime, u32 *breakCount);
+                       bool isLeft, bool isComptime, u32 *breakCount,bool *shouldContinue);
-- 
cgit v1.2.3