]> git.seodisparate.com - c_simple_http/commitdiff
Impl. FOREACH and NOTFOREACH
authorStephen Seo <seo.disparate@gmail.com>
Tue, 19 Nov 2024 11:53:11 +0000 (20:53 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Tue, 19 Nov 2024 11:53:11 +0000 (20:53 +0900)
TODO: Figure out nested FOREACH or abandon that feature.

example_config/example.config
src/http_template.c

index 5ce64e596a01c73870c86a792ae6cc287a6f45cb..4bf5ebf5991ede180eaa8a37279a5cb5310cea53 100644 (file)
@@ -1,80 +1,54 @@
 PATH=/
 HTML='''
-  <html>
-  <head>
-  <style>
-    body {
-    color: #FFF;
-    background-color: #333;
-  }
-  a {
-    color: #AAF;
-  }
-  a:link {
-    color: #AAF;
-  }
-  a:visited {
-    color: #88B;
-  }
-  a:focus a:hover {
-    color: #DDF;
-  }
-  a:active {
-    color: #FFF;
-  }
-  </style>
-  </head>
-  <body>
-  <h1>Test HTML</h1><br>
-  <h2>{{{Var}}}</h2>
-  <h3><a href="/inner">To inner.</a></h3>
-  <h4>Tests:</h4><br>
-  {{{!IF ThisValue==false}}}
-    ThisValue is <b>false</b>.
+  <h4>For Each test:</h4><br>
+  {{{!FOREACH EachTestHead!EachTestMid!EachTestTail}}}
+    {{{!INDEX ArrayValue[0]}}}
     <br>
-    {{{!IF ThisValue==false}}}
-    Nested0 ThisValue is <b>false</b>.
-    {{{!ELSEIF ThisValue==true}}}
-    Nested0 ThisValue is <b>true</b>.
-    {{{!ELSE}}}
-    Nested0 ThisValue is invalid.
-    {{{!ENDIF}}}
-  {{{!ELSEIF ThisValue==true}}}
-    ThisValue is <b>true</b>.
+    {{{EachTestHead}}}
     <br>
-    {{{!IF ThisValue==false}}}
-    Nested1 ThisValue is <b>false</b>.
-    {{{!ELSEIF ThisValue==true}}}
-    Nested1 ThisValue is <b>true</b>.
-    {{{!ELSE}}}
-    Nested1 ThisValue is invalid.
-    {{{!ENDIF}}}
-  {{{!ELSE}}}
-    ThisValue is invalid.
+    {{{!INDEX ArrayValue[1]}}}
     <br>
-    {{{!IF ThisValue==false}}}
-    Nested2 ThisValue is <b>false</b>.
-    {{{!ELSEIF ThisValue==true}}}
-    Nested2 ThisValue is <b>true</b>.
+    {{{EachTestMid}}}
+    <br>
+    {{{!IF ThisValue==true}}}
+    ThisValue is <b>true</b>.
+        {{{!IF ThisOtherValue==true}}}
+        ThisOtherValue is true.
+        {{{!ELSE}}}
+        ThisOtherValue is false.
+        {{{!ENDIF}}}
     {{{!ELSE}}}
-    Nested2 ThisValue is invalid.
+    ThisValue is <b>NOT true</b>.
     {{{!ENDIF}}}
-  {{{!ENDIF}}}
-
-  <h4>Array Value:</h4><br>
-  {{{!INDEX ArrayValue[2]}}}
-  {{{!INDEX ArrayValue[0]}}}
-  {{{!INDEX ArrayValue[1]}}}
+    <br>
+    {{{EachTestTail}}}
+    <br>
+    {{{!INDEX ArrayValue[2]}}}
+    <br>
+  {{{!ENDFOREACH}}}
   </body>
 '''
 
 Var='''Test var value'''
-ThisValue=derp
+ThisValue=true
+ThisOtherValue=true
 
 ArrayValue=One_IDX_Zero
 ArrayValue=Two_IDX_One
 ArrayValue=Three_IDX_Two
 
+EachTestHead='''First Entry Head'''
+EachTestMid='''First Entry Mid'''
+EachTestTail='''First Entry Tail'''
+
+EachTestHead='''Second Entry Head'''
+EachTestMid='''Second Entry Mid'''
+EachTestTail='''Second Entry Tail'''
+
+EachTestHead='''Third Entry Head'''
+EachTestMid='''Third Entry Mid'''
+EachTestTail='''Third Entry Tail'''
+
 PATH=/inner
 HTML_FILE='''example_config/inner.html'''
 VAR_FILE='''example_config/var.html'''
index 6a97359eebb42a5a7785c3e2f32656c2067c23c4..0990cd910fc467dc4e46d4f877d4ac871e87f4bc 100644 (file)
@@ -14,6 +14,8 @@
 // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+// TODO: Update files map, and cleanup DEBUG prints.
+
 #include "http_template.h"
 
 // Standard library includes.
 #include "config.h"
 #include "helpers.h"
 
+typedef struct C_SIMPLE_HTTP_ArrayVar {
+  // Should be the value of the variable, or filename if var ends with _FILE.
+  char *value;
+  struct C_SIMPLE_HTTP_ArrayVar *next;
+} C_SIMPLE_HTTP_ArrayVar;
+
+typedef struct C_SIMPLE_HTTP_ForState {
+  uint64_t idx;
+  // Key is variable name, Value is ArrayVar struct.
+  SDArchiverHashMap *array_vars;
+  uint64_t buf_start_idx;
+} C_SIMPLE_HTTP_ForState;
+
+// Function forward declaration.
+int c_simple_http_internal_handle_inside_delimeters(
+    uint32_t *state,
+    const char *html_buf,
+    const size_t html_buf_idx,
+    const char *var,
+    const size_t var_size,
+    SDArchiverLinkedList *if_state_stack,
+    SDArchiverLinkedList *for_state_stack,
+    const C_SIMPLE_HTTP_ParsedConfig *wrapped_hash_map,
+    SDArchiverLinkedList *string_part_list,
+    SDArchiverLinkedList **files_list_out);
+
+void c_simple_http_internal_cleanup_ArrayVar(
+    C_SIMPLE_HTTP_ArrayVar **array_var) {
+  if (*array_var) {
+    if ((*array_var)->next) {
+      c_simple_http_internal_cleanup_ArrayVar(&(*array_var)->next);
+    }
+    if ((*array_var)->value) {
+      free((*array_var)->value);
+    }
+    free(*array_var);
+    *array_var = NULL;
+  }
+}
+
+void c_simple_http_internal_cleanup_ArrayVar_void(void *data) {
+  c_simple_http_internal_cleanup_ArrayVar((C_SIMPLE_HTTP_ArrayVar**)&data);
+}
+
+void c_simple_http_internal_cleanup_ForState(C_SIMPLE_HTTP_ForState **state) {
+  if (*state) {
+    if ((*state)->array_vars) {
+      simple_archiver_hash_map_free(&(*state)->array_vars);
+    }
+    free(*state);
+    *state = NULL;
+  }
+}
+
+void c_simple_http_internal_cleanup_ForState_void(void *data) {
+  c_simple_http_internal_cleanup_ForState((C_SIMPLE_HTTP_ForState**)&data);
+}
+
+int c_simple_http_internal_free_exact_ForState_in_list(void *data, void *ud) {
+  C_SIMPLE_HTTP_ForState *for_state = data;
+  C_SIMPLE_HTTP_ForState *state_was_top = ud;
+  return for_state == state_was_top ? 1 : 0;
+}
+
 /// Returns 0 if "c_string" ends with "_FILE".
 int c_simple_http_internal_ends_with_FILE(const char *c_string) {
   if (!c_string) {
@@ -55,6 +121,38 @@ int c_simple_http_internal_always_return_one(
   return 1;
 }
 
+/// If non-NULL, must not be free'd.
+char *c_simple_http_internal_get_for_var(SDArchiverLinkedList *for_state_stack,
+                                         const char *var) {
+  if (for_state_stack->count == 0) {
+    return NULL;
+  }
+  SDArchiverLLNode *node = for_state_stack->head;
+  while (node) {
+    node = node->next;
+    if (node && node->data) {
+      C_SIMPLE_HTTP_ForState *for_state = node->data;
+      uint64_t idx = for_state->idx;
+      C_SIMPLE_HTTP_ArrayVar *array_var =
+        simple_archiver_hash_map_get(for_state->array_vars,
+                                     var,
+                                     strlen(var) + 1);
+      if (!array_var) {
+        continue;
+      }
+      for(; idx-- > 0;) {
+        array_var = array_var->next;
+        if (!array_var) {
+          return NULL;
+        }
+      }
+      return array_var->value;
+    }
+  }
+
+  return NULL;
+}
+
 int c_simple_http_internal_parse_if_expression(
     const C_SIMPLE_HTTP_ParsedConfig *wrapped_hash_map,
     const char *var,
@@ -235,13 +333,211 @@ int c_simple_http_internal_parse_if_expression(
   return 0;
 }
 
+int c_simple_http_internal_populate_array_vars(
+    char **for_each_var_name,
+    const C_SIMPLE_HTTP_HashMapWrapper *wrapped_hash_map,
+    const char *var,
+    C_SIMPLE_HTTP_ForState *for_state) {
+  C_SIMPLE_HTTP_ConfigValue *config_value =
+    simple_archiver_hash_map_get(wrapped_hash_map->hash_map,
+                                 *for_each_var_name,
+                                 strlen(*for_each_var_name) + 1);
+  if (!config_value || !config_value->value) {
+    fprintf(
+      stderr,
+      "ERROR Given var name \"%s\" not in config! %s\n",
+      *for_each_var_name,
+      var);
+    return 1;
+  }
+  C_SIMPLE_HTTP_ArrayVar *array_var =
+    malloc(sizeof(C_SIMPLE_HTTP_ArrayVar));
+  array_var->next = NULL;
+  C_SIMPLE_HTTP_ArrayVar *current_array_var = array_var;
+
+  const uint_fast8_t is_ends_with_FILE =
+    c_simple_http_internal_ends_with_FILE(*for_each_var_name) == 0
+      ? 1 : 0;
+
+  if (is_ends_with_FILE) {
+    array_var->value = c_simple_http_FILE_to_c_str(config_value->value, NULL);
+    if (!array_var->value) {
+      fprintf(stderr,
+              "ERROR Failed to read from file \"%s\"! %s\n",
+              config_value->value,
+              var);
+      c_simple_http_internal_cleanup_ArrayVar(&array_var);
+      return 1;
+    }
+  } else {
+    array_var->value = strdup(config_value->value);
+  }
+
+  while (config_value) {
+    config_value = config_value->next;
+    if (config_value) {
+      current_array_var->next =
+        malloc(sizeof(C_SIMPLE_HTTP_ArrayVar));
+      current_array_var = current_array_var->next;
+      current_array_var->next = NULL;
+      if (is_ends_with_FILE) {
+        current_array_var->value =
+          c_simple_http_FILE_to_c_str(config_value->value, NULL);
+        if (!current_array_var->value) {
+          fprintf(stderr,
+                  "ERROR Failed to read from file \"%s\"! %s\n",
+                  config_value->value,
+                  var);
+          c_simple_http_internal_cleanup_ArrayVar(&array_var);
+          return 1;
+        }
+      } else {
+        current_array_var->value = strdup(config_value->value);
+      }
+    }
+  }
+
+  if (simple_archiver_hash_map_insert(
+      for_state->array_vars,
+      array_var,
+      *for_each_var_name,
+      strlen(*for_each_var_name) + 1,
+      c_simple_http_internal_cleanup_ArrayVar_void,
+      NULL) != 0) {
+    fprintf(stderr, "ERROR Internal failed to add array_var! %s\n", var);
+    *for_each_var_name = NULL;
+    return 1;
+  }
+
+  return 0;
+}
+
+int c_simple_http_internal_parse_iterate(
+    uint32_t *state,
+    const char *html_buf,
+    uint64_t idx,
+    size_t *delimeter_count,
+    size_t *last_template_idx,
+    SDArchiverLinkedList *if_state_stack,
+    SDArchiverLinkedList *for_state_stack,
+    SDArchiverLinkedList *string_part_list,
+    const C_SIMPLE_HTTP_ParsedConfig *wrapped_hash_map,
+    SDArchiverLinkedList **files_list_out) {
+  C_SIMPLE_HTTP_String_Part string_part;
+  if (((*state) & 1) == 0) {
+    // Using 0x7B instead of left curly-brace due to bug in vim navigation.
+    if (html_buf[idx] == 0x7B) {
+      ++(*delimeter_count);
+      if ((*delimeter_count) >= 3) {
+        (*delimeter_count) = 0;
+        (*state) |= 1;
+        uint32_t *if_state_stack_head = if_state_stack->count == 0 ? NULL :
+          if_state_stack->head->next->data;
+        if ((!if_state_stack_head
+            || ((*if_state_stack_head) & 7) == 1
+              || ((*if_state_stack_head) & 7) == 3
+              || ((*if_state_stack_head) & 7) == 5)
+            && (for_state_stack->count == 0 || ((*state) & 4) != 0)) {
+          if (string_part_list->count != 0 && ((*state) & 2) == 0) {
+            C_SIMPLE_HTTP_String_Part *last_part =
+              string_part_list->tail->prev->data;
+            (*last_template_idx) = last_part->extra;
+          } else if (((*state) & 2) != 0) {
+            // Only skip first "last_part->extra" in FOREACH.
+            *state &= 0xFFFFFFFD;
+          }
+          string_part.size = idx - (*last_template_idx) - 1;
+          string_part.buf = malloc(string_part.size);
+          memcpy(
+            string_part.buf,
+            html_buf + (*last_template_idx),
+            string_part.size);
+          string_part.buf[string_part.size - 1] = 0;
+          string_part.extra = idx + 1;
+          c_simple_http_add_string_part(string_part_list,
+                                        string_part.buf,
+                                        string_part.extra);
+          free(string_part.buf);
+        } else {
+          c_simple_http_add_string_part(string_part_list, NULL, idx + 1);
+        }
+      }
+    } else {
+      (*delimeter_count) = 0;
+    }
+  } else {
+    // (state & 1) is 1
+    // Using 0x7D instead of right curly-brace due to bug in vim navigation.
+    if (html_buf[idx] == 0x7D) {
+      ++(*delimeter_count);
+      if ((*delimeter_count) >= 3) {
+        (*delimeter_count) = 0;
+        (*state) &= 0xFFFFFFFE;
+        C_SIMPLE_HTTP_String_Part *last_part =
+          string_part_list->tail->prev->data;
+        const size_t var_size = idx - 2 - last_part->extra;
+        __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
+        char *var = malloc(var_size + 1);
+        memcpy(
+          var,
+          html_buf + last_part->extra,
+          var_size);
+        var[var_size] = 0;
+        if (c_simple_http_internal_handle_inside_delimeters(
+              state,
+              html_buf,
+              idx,
+              var,
+              var_size,
+              if_state_stack,
+              for_state_stack,
+              wrapped_hash_map,
+              string_part_list,
+              files_list_out) != 0) {
+          return 1;
+        }
+      }
+    } else {
+      (*delimeter_count) = 0;
+    }
+  }
+
+  return 0;
+}
+
+int c_simple_http_internal_for_hash_iter(const void *key,
+                                         size_t size,
+                                         const void *value,
+                                         void *ud) {
+  const C_SIMPLE_HTTP_ArrayVar *array_var = value;
+  C_SIMPLE_HTTP_ForState *for_state = ud;
+
+  for (size_t count = 0; count < for_state->idx; ++count) {
+    array_var = array_var->next;
+    if (!array_var) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+/// Returns zero if all ArrayVars can reach the idx in ForState.
+int c_simple_http_internal_for_has_idx(C_SIMPLE_HTTP_ForState *state) {
+  return simple_archiver_hash_map_iter(state->array_vars,
+                                       c_simple_http_internal_for_hash_iter,
+                                       state);
+}
+
 /// Returns zero on success.
 int c_simple_http_internal_handle_inside_delimeters(
     uint32_t *state,
+    const char *html_buf,
     const size_t html_buf_idx,
     const char *var,
     const size_t var_size,
     SDArchiverLinkedList *if_state_stack,
+    SDArchiverLinkedList *for_state_stack,
     const C_SIMPLE_HTTP_ParsedConfig *wrapped_hash_map,
     SDArchiverLinkedList *string_part_list,
     SDArchiverLinkedList **files_list_out) {
@@ -252,6 +548,10 @@ int c_simple_http_internal_handle_inside_delimeters(
   } else if (var[0] == '!') {
     // Is an expression.
     if (strncmp(var + 1, "IF ", 3) == 0) {
+      if (for_state_stack->count != 0 && ((*state) & 0x4) == 0) {
+        // Skip if in FOREACH but not iterating within FOREACH yet.
+        return 0;
+      }
       __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
       char *left_side = NULL;
       __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
@@ -308,6 +608,10 @@ int c_simple_http_internal_handle_inside_delimeters(
       simple_archiver_list_add_front(if_state_stack, state, NULL);
       c_simple_http_add_string_part(string_part_list, NULL, html_buf_idx + 1);
     } else if (strncmp(var + 1, "ELSEIF ", 7) == 0) {
+      if (for_state_stack->count != 0 && ((*state) & 0x4) == 0) {
+        // Skip if in FOREACH but not iterating within FOREACH yet.
+        return 0;
+      }
       if (if_state_stack->count == 0) {
         fprintf(stderr, "ERROR No previous conditional! %s\n", var);
         return 1;
@@ -373,6 +677,10 @@ int c_simple_http_internal_handle_inside_delimeters(
       }
       c_simple_http_add_string_part(string_part_list, NULL, html_buf_idx + 1);
     } else if (strncmp(var + 1, "ELSE", 4) == 0) {
+      if (for_state_stack->count != 0 && ((*state) & 0x4) == 0) {
+        // Skip if in FOREACH but not iterating within FOREACH yet.
+        return 0;
+      }
       if (if_state_stack->count == 0) {
         fprintf(stderr, "ERROR No previous IF! %s\n", var);
         return 1;
@@ -389,6 +697,10 @@ int c_simple_http_internal_handle_inside_delimeters(
       }
       c_simple_http_add_string_part(string_part_list, NULL, html_buf_idx + 1);
     } else if (strncmp(var + 1, "ENDIF", 5) == 0) {
+      if (for_state_stack->count != 0 && ((*state) & 0x4) == 0) {
+        // Skip if in FOREACH but not iterating within FOREACH yet.
+        return 0;
+      }
       if (if_state_stack->count == 0) {
         fprintf(stderr, "ERROR No previous IF! %s\n", var);
         return 1;
@@ -399,6 +711,11 @@ int c_simple_http_internal_handle_inside_delimeters(
     } else if (strncmp(var + 1, "INDEX ", 6) == 0) {
       // Indexing into variable array.
 
+      if (for_state_stack->count != 0 && ((*state) & 0x4) == 0) {
+        // Skip if in FOREACH but not iterating within FOREACH yet.
+        return 0;
+      }
+
       __attribute__((cleanup(simple_archiver_list_free)))
       SDArchiverLinkedList *var_parts = simple_archiver_list_init();
       char buf[64];
@@ -501,19 +818,205 @@ int c_simple_http_internal_handle_inside_delimeters(
                                           value_contents_size + 1,
                                           html_buf_idx + 1);
     } else if (strncmp(var + 1, "FOREACH ", 8) == 0) {
-      // TODO
-      fprintf(stderr, "ERROR Unimplemented! %s\n", var);
-      return 1;
+      if (for_state_stack->count != 0 && ((*state) & 4) == 0) {
+        // Don't parse if nested and not running in nested iteration.
+        return 0;
+      }
+
+      __attribute__((cleanup(c_simple_http_internal_cleanup_ForState)))
+      C_SIMPLE_HTTP_ForState *for_state =
+        malloc(sizeof(C_SIMPLE_HTTP_ForState));
+      for_state->array_vars = simple_archiver_hash_map_init();
+      for_state->idx = 0;
+      for_state->buf_start_idx = html_buf_idx + 1;
+
+      char buf[64];
+      size_t buf_idx = 0;
+      __attribute__((cleanup(simple_archiver_list_free)))
+      SDArchiverLinkedList *parts = simple_archiver_list_init();
+
+      size_t idx = 1 + 8;
+      for (; idx < var_size; ++idx) {
+        if (var[idx] != '!') {
+          buf[buf_idx++] = var[idx];
+          if (buf_idx >= 63) {
+            buf[63] = 0;
+            c_simple_http_add_string_part(parts, buf, 0);
+            buf_idx = 0;
+          }
+        } else {
+          char *for_each_var_name = NULL;
+          if (parts->count != 0) {
+            if (buf_idx != 0) {
+              buf[buf_idx] = 0;
+              c_simple_http_add_string_part(parts, buf, 0);
+              buf_idx = 0;
+            }
+            for_each_var_name = c_simple_http_combine_string_parts(parts);
+            simple_archiver_list_free(&parts);
+            parts = simple_archiver_list_init();
+          } else {
+            if (buf_idx == 0) {
+              fprintf(
+                stderr, "ERROR Expected var name before \"!\"! %s\n", var);
+              return 1;
+            }
+            buf[buf_idx] = 0;
+            for_each_var_name = strdup(buf);
+            buf_idx = 0;
+          }
+
+          if(c_simple_http_internal_populate_array_vars(&for_each_var_name,
+                                                        wrapped_hash_map,
+                                                        var,
+                                                        for_state) != 0) {
+            if (for_each_var_name) {
+              free(for_each_var_name);
+            }
+            return 1;
+          }
+        }
+      }
+      if (buf_idx != 0 || parts->count != 0) {
+        // Var-name remaining at end of "var".
+        char *for_each_var_name = NULL;
+        if (parts->count != 0) {
+          if (buf_idx != 0) {
+            buf[buf_idx] = 0;
+            c_simple_http_add_string_part(parts, buf, 0);
+            buf_idx = 0;
+          }
+          for_each_var_name = c_simple_http_combine_string_parts(parts);
+          simple_archiver_list_free(&parts);
+          parts = simple_archiver_list_init();
+        } else {
+          if (buf_idx == 0) {
+            fprintf(
+              stderr, "ERROR Expected var name before \"!\"! %s\n", var);
+            return 1;
+          }
+          buf[buf_idx] = 0;
+          for_each_var_name = strdup(buf);
+          buf_idx = 0;
+        }
+
+        if(c_simple_http_internal_populate_array_vars(&for_each_var_name,
+                                                      wrapped_hash_map,
+                                                      var,
+                                                      for_state) != 0) {
+          if (for_each_var_name) {
+            free(for_each_var_name);
+          }
+          return 1;
+        }
+      }
+
+      simple_archiver_list_add_front(
+        for_state_stack,
+        for_state,
+        c_simple_http_internal_cleanup_ForState_void);
+      for_state = NULL;
+      c_simple_http_add_string_part(string_part_list, NULL, html_buf_idx + 1);
+      fprintf(stderr,
+              "DEBUG FOREACH for_state_stack count is %lu\n",
+              for_state_stack->count);
     } else if (strncmp(var + 1, "ENDFOREACH", 10) == 0) {
-      // TODO
-      fprintf(stderr, "ERROR Unimplemented! %s\n", var);
-      return 1;
+      if (for_state_stack->count == 0) {
+        fprintf(stderr, "ERROR ENDFOREACH but no FOREACH!\n");
+        return 1;
+      } else if (for_state_stack->count != 0 && ((*state) & 4) != 0) {
+        // Don't parse if nested and running in nested iteration.
+        return 0;
+      }
+
+      C_SIMPLE_HTTP_ForState *state_top = for_state_stack->head->next->data;
+
+      const uint64_t end_idx = html_buf_idx + 1 - 3 - 10 - 1 - 3;
+      if (end_idx < state_top->buf_start_idx) {
+        fprintf(stderr, "ERROR Internal invalid idx! %s\n", var);
+        return 1;
+      }
+
+      while (1) {
+        // xxxx xxx0 - Initial state.
+        // xxxx xxx1 - Three left-curly-brace delimeter reached.
+        // xxxx xx1x - Don't update last_template_idx.
+        // xxxx x1xx - Nested in ForEach.
+        uint32_t inner_state = 6;
+        size_t delimeter_count = 0;
+        size_t last_template_idx = state_top->buf_start_idx;
+        for (uint64_t idx = state_top->buf_start_idx; idx < end_idx; ++idx) {
+          if (c_simple_http_internal_parse_iterate(&inner_state,
+                                                   html_buf,
+                                                   idx,
+                                                   &delimeter_count,
+                                                   &last_template_idx,
+                                                   if_state_stack,
+                                                   for_state_stack,
+                                                   string_part_list,
+                                                   wrapped_hash_map,
+                                                   files_list_out) != 0) {
+            return 1;
+          }
+        }
+
+        C_SIMPLE_HTTP_String_Part *last_part =
+          string_part_list->tail->prev->data;
+        if (end_idx > last_part->extra) {
+          size_t size = end_idx - last_part->extra - 1;
+          char *buf = malloc(size + 1);
+          memcpy(buf, html_buf + last_part->extra, size);
+          buf[size] = 0;
+          c_simple_http_add_string_part(string_part_list, buf, end_idx + 1);
+          free(buf);
+        }
+
+        ++state_top->idx;
+
+        if (c_simple_http_internal_for_has_idx(state_top) != 0) {
+          break;
+        }
+      }
+
+      //simple_archiver_list_remove_once(
+      //  for_state_stack,
+      //  c_simple_http_internal_always_return_one,
+      //  NULL);
+
+      // Top may no longer refer to "state_top" when nested FOREACH is added,
+      // so pointer comparison is necessary to remove the correct "state_top".
+      if(simple_archiver_list_remove_once(
+          for_state_stack,
+          c_simple_http_internal_free_exact_ForState_in_list,
+          state_top) != 1) {
+        fprintf(stderr, "ERROR Failed to free a single \"ForState\" entry!\n");
+        return 1;
+      }
+      c_simple_http_add_string_part(string_part_list, NULL, html_buf_idx + 1);
+      fprintf(stderr,
+              "DEBUG ENDFOREACH for_state_stack count is %lu\n",
+              for_state_stack->count);
     } else {
       fprintf(stderr, "ERROR Invalid expression! %s\n", var);
       return 1;
     }
   } else {
     // Refers to a variable by name.
+
+    // Check if ForEach variable.
+    if (for_state_stack->count != 0 && ((*state) & 0x4) != 0) {
+      char *value = c_simple_http_internal_get_for_var(for_state_stack, var);
+      if (value) {
+        c_simple_http_add_string_part(string_part_list,
+                                      value,
+                                      html_buf_idx + 1);
+        return 0;
+      }
+    } else if (((*state) & 4) == 0) {
+      c_simple_http_add_string_part(string_part_list, NULL, html_buf_idx + 1);
+      return 0;
+    }
+
     C_SIMPLE_HTTP_ConfigValue *config_value =
       simple_archiver_hash_map_get(
         wrapped_hash_map->hash_map,
@@ -645,6 +1148,8 @@ char *c_simple_http_path_to_generated(
 
   // xxxx xxx0 - Initial state, no delimeter reached.
   // xxxx xxx1 - Three left-curly-brace delimeters reached.
+  // xxxx xx1x - Don't update last_template_idx.
+  // xxxx x1xx - Nested in ForEach.
   uint32_t state = 0;
 
   // xxxx x001 - If expression ALLOW contents.
@@ -658,77 +1163,22 @@ char *c_simple_http_path_to_generated(
   __attribute__((cleanup(simple_archiver_list_free)))
   SDArchiverLinkedList *if_state_stack = simple_archiver_list_init();
 
+  // List values are ForState structs.
+  __attribute__((cleanup(simple_archiver_list_free)))
+  SDArchiverLinkedList *for_state_stack = simple_archiver_list_init();
+
   for (; idx < html_buf_size; ++idx) {
-    if ((state & 1) == 0) {
-      // Using 0x7B instead of left curly-brace due to bug in vim navigation.
-      if (html_buf[idx] == 0x7B) {
-        ++delimeter_count;
-        if (delimeter_count >= 3) {
-          delimeter_count = 0;
-          state |= 1;
-          uint32_t *if_state_stack_head = if_state_stack->count == 0 ? NULL :
-            if_state_stack->head->next->data;
-          if (!if_state_stack_head
-              || ((*if_state_stack_head) & 7) == 1
-                || ((*if_state_stack_head) & 7) == 3
-                || ((*if_state_stack_head) & 7) == 5) {
-            if (string_part_list->count != 0) {
-              C_SIMPLE_HTTP_String_Part *last_part =
-                string_part_list->tail->prev->data;
-              last_template_idx = last_part->extra;
-            }
-            string_part.size = idx - last_template_idx - 1;
-            string_part.buf = malloc(string_part.size);
-            memcpy(
-              string_part.buf,
-              html_buf + last_template_idx,
-              string_part.size);
-            string_part.buf[string_part.size - 1] = 0;
-            string_part.extra = idx + 1;
-            c_simple_http_add_string_part(string_part_list,
-                                          string_part.buf,
-                                          string_part.extra);
-            free(string_part.buf);
-          } else {
-            c_simple_http_add_string_part(string_part_list, NULL, idx + 1);
-          }
-        }
-      } else {
-        delimeter_count = 0;
-      }
-    } else {
-      // (state & 1) is 1
-      // Using 0x7D instead of right curly-brace due to bug in vim navigation.
-      if (html_buf[idx] == 0x7D) {
-        ++delimeter_count;
-        if (delimeter_count >= 3) {
-          delimeter_count = 0;
-          state &= 0xFFFFFFFE;
-          C_SIMPLE_HTTP_String_Part *last_part =
-            string_part_list->tail->prev->data;
-          const size_t var_size = idx - 2 - last_part->extra;
-          __attribute__((cleanup(simple_archiver_helper_cleanup_c_string)))
-          char *var = malloc(var_size + 1);
-          memcpy(
-            var,
-            html_buf + last_part->extra,
-            var_size);
-          var[var_size] = 0;
-          if (c_simple_http_internal_handle_inside_delimeters(
-                &state,
-                idx,
-                var,
-                var_size,
-                if_state_stack,
-                wrapped_hash_map,
-                string_part_list,
-                files_list_out) != 0) {
-            return NULL;
-          }
-        }
-      } else {
-        delimeter_count = 0;
-      }
+    if(c_simple_http_internal_parse_iterate(&state,
+                                            html_buf,
+                                            idx,
+                                            &delimeter_count,
+                                            &last_template_idx,
+                                            if_state_stack,
+                                            for_state_stack,
+                                            string_part_list,
+                                            wrapped_hash_map,
+                                            files_list_out) != 0) {
+      return NULL;
     }
   }