Edit

kc3-lang/kc3/ekc3/ekc3.c

Branch :

  • ekc3/ekc3.c
  • /* kc3
     * Copyright from 2022 to 2025 kmx.io <contact@kmx.io>
     *
     * Permission is hereby granted to use this software granted the above
     * copyright notice and this permission paragraph are included in all
     * copies and substantial portions of this software.
     *
     * THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
     * PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
     * AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
     * THIS SOFTWARE.
     */
    #include <string.h>
    #include "../libkc3/kc3.h"
    #include <unistd.h>
    #include "ekc3.h"
    #include "html.h"
    
    s_tag * ekc3_parse_template (s_buf *input, s_tag *dest)
    {
      s_buf buf = {0};
      character c;
      e_ekc3_state state;
      s_tag tmp = {0};
      s_buf token_buf = {0};
      u8    token_position;
      s_str token_str = {0};
      if (! input || ! dest) {
        err_puts("ekc3_parse_template_from_stream: invalid arguments");
        return NULL;
      }
      if (! buf_init_alloc(&buf, BUF_SIZE)) {
        err_puts("ekc3_parse_template_from_stream: buf_init_alloc result");
        return NULL;
      }
      if (! buf_init_alloc(&token_buf, BUF_SIZE)) {
        err_puts("ekc3_parse_template_from_stream: buf_init_alloc token");
        buf_clean(&buf);
        return NULL;
      }
      state = EKC3_STATE_RAW;
      token_position = 0;
      while (true) {
        if (buf_read_character_utf8(input, &c) <= 0) {
          switch (state) {
          case EKC3_STATE_RAW:
            if (buf_write_character_utf8(&buf, '\n') <= 0 ||
                buf_write_1(&buf, "EKC3.verbose ") <= 0 ||
                buf_read_to_str(&token_buf, &token_str) <= 0)
              goto error;
            if (buf_inspect_str(&buf, &token_str) <= 0) {
              str_clean(&token_str);
              goto error;
            }
            str_clean(&token_str);
            break;
          case EKC3_STATE_VERBOSE:
            if (buf_write_1(&buf, "\nEKC3.verbose ") <= 0 ||
                buf_read_to_str(&token_buf, &token_str) <= 0)
              goto error;
            if (buf_write_str(&buf, &token_str) <= 0) {
              str_clean(&token_str);
              goto error;
            }
            str_clean(&token_str);
            break;
          case EKC3_STATE_SILENT:
            if (buf_read_to_str(&token_buf, &token_str) <= 0)
              goto error;
            if (buf_write_str(&buf, &token_str) <= 0) {
              str_clean(&token_str);
              goto error;
            }
            str_clean(&token_str);
            break;
          }
          break;
        }
        switch (state) {
        case EKC3_STATE_RAW:
          switch (token_position) {
          case 0:
            if (c == '<') {
              token_position = 1;
            } else {
              if (buf_write_character_utf8(&token_buf, c) <= 0) {
                goto error;
              }
            }
            break;
          case 1:
            if (c == '%') {
              token_position = 2;
            } else {
              token_position = 0;
              if (buf_write_character_utf8(&token_buf, '<') <= 0 ||
                  buf_write_character_utf8(&token_buf, c) <= 0) {
                goto error;
              }
            }
            break;
          case 2:
            if (buf_write_character_utf8(&buf, '\n') <= 0 ||
                buf_write_1(&buf, "EKC3.verbose ") <= 0)
              goto error;
            if (buf_read_to_str(&token_buf, &token_str) <= 0)
              goto error;
            if (buf_inspect_str(&buf, &token_str) <= 0) {
              str_clean(&token_str);
              goto error;
            }
            str_clean(&token_str);
            buf_empty(&token_buf);
            if (c == '=') {
              state = EKC3_STATE_VERBOSE;
              token_position = 0;
            } else {
              state = EKC3_STATE_SILENT;
              token_position = 0;
              if (buf_write_character_utf8(&token_buf, c) <= 0) {
                goto error;
              }
            }
            break;
          }
          break;
        case EKC3_STATE_SILENT:
        case EKC3_STATE_VERBOSE:
          switch (token_position) {
          case 0:
            if (c == '%')
              token_position = 1;
            else if (buf_write_character_utf8(&token_buf, c) <= 0)
              goto error;
            break;
          case 1:
            if (c == '>') {
              if (buf_read_to_str(&token_buf, &token_str) <= 0)
                goto error;
              if (state == EKC3_STATE_VERBOSE &&
                  buf_write_1(&buf, "\nEKC3.verbose ") <= 0) {
                str_clean(&token_str);
                goto error;
              }
              if (buf_write_str(&buf, &token_str) <= 0) {
                str_clean(&token_str);
                goto error;
              }
              str_clean(&token_str);
              state = EKC3_STATE_RAW;
              token_position = 0;
              buf_empty(&token_buf);
            }
            else {
              if (buf_write_character_utf8(&token_buf, '%') <= 0 ||
                  buf_write_character_utf8(&token_buf, c) <= 0)
                goto error;
              token_position = 0;
            }
            break;
          }
          break;
        }
      }
      if (buf_parse_tag(&buf, &tmp) <= 0) {
        err_puts("ekc3_parse_template: buf_parse_tag");
        err_inspect_buf(&buf);
        assert(! "ekc3_parse_template: buf_parse_tag");
        goto error;
      }
      buf_clean(&token_buf);
      buf_clean(&buf);
      *dest = tmp;
      return dest;
     error:
      buf_clean(&token_buf);
      buf_clean(&buf);
      return NULL;
    }
    
    s_tag * ekc3_parse_template_1 (const char *input, s_tag *dest)
    {
      s_buf input_buf;
      uw len;
      assert(input);
      assert(dest);
      if (! input || ! dest) {
        err_puts("ekc3_parse_template_1: invalid arguments");
        assert(! "ekc3_parse_template_1: invalid arguments");
        return NULL;
      }
      len = strlen(input);
      if (! buf_init_const(&input_buf, len, input)) {
        err_puts("ekc3_parse_template_1: buf_init_const");
        assert(! "ekc3_parse_template_1: buf_init_const");
        return NULL;
      }
      input_buf.wpos = len;
      return ekc3_parse_template(&input_buf, dest);
    }
    
    s_tag * ekc3_parse_template_str (const s_str *input, s_tag *dest)
    {
      s_buf input_buf;
      assert(input);
      assert(dest);
      if (! input || ! dest) {
        err_puts("ekc3_parse_template_str: invalid arguments");
        assert(! "ekc3_parse_template_str: invalid arguments");
        return NULL;
      }
      if (! buf_init_const(&input_buf, input->size, input->ptr.pchar)) {
        err_puts("ekc3_parse_template_str: buf_init_const");
        assert(! "ekc3_parse_template_str: buf_init_const");
        return NULL;
      }
      input_buf.wpos = input->size;
      return ekc3_parse_template(&input_buf, dest);
    }
    
    /*
    s_list *** ekc3_append_and_empty_buf (s_list ***tail, s_buf *buf)
    {
      s_str str;
      assert(tail);
      assert(*tail);
      assert(! **tail);
      assert(buf);
      if (buf_read_to_str(buf, &str) <= 0) {
        err_puts("ekc3_append_and_empty_buf: buf_read_to_str");
        assert(! "ekc3_append_and_empty_buf: buf_read_to_str");
        return NULL;
      }
      if (! str.size) {
        buf_empty(buf);
        return tail;
      }
      **tail = list_new(NULL);
      if (! **tail) {
        str_clean(&str);
        err_puts("ekc3_append_and_empty_buf: list_new");
        assert(! "ekc3_append_and_empty_buf: list_new");
        return NULL;
      }
      (**tail)->tag.type = TAG_STR;
      (**tail)->tag.data.str = str;
      buf_empty(buf);
      *tail = &(**tail)->next.data.plist;
      return tail;
    }
    
    s_list *** ekc3_append_do_block (s_list ***tail, s_do_block *do_block)
    {
      assert(tail);
      assert(*tail);
      assert(! **tail);
      assert(do_block);
      **tail = list_new(NULL);
      if (! **tail)
        return NULL;
      (**tail)->tag.type = TAG_DO_BLOCK;
      (**tail)->tag.data.do_block = *do_block;
      *tail = &(**tail)->next.data.plist;
      return tail;
    }
    
    s_list *** ekc3_append_silent_do_block (s_list ***tail,
                                            s_do_block *do_block)
    {
      assert(tail);
      assert(*tail);
      assert(! **tail);
      assert(do_block);
      if (! ekc3_append_sym(tail, sym_1("silent")))
        return NULL;
      if (! ekc3_append_do_block(tail, do_block))
        return NULL;
      return tail;
    }
    
    s_list *** ekc3_append_str (s_list ***tail, s_str *str)
    {
      assert(tail);
      assert(*tail);
      assert(! **tail);
      assert(str);
      **tail = list_new(NULL);
      if (! *tail)
        return NULL;
      (**tail)->tag.type = TAG_STR;
      (**tail)->tag.data.str = *str;
      *tail = &(**tail)->next.data.plist;
      return tail;
    }
    
    s_list *** ekc3_append_sym (s_list ***tail, const s_sym *sym)
    {
      assert(tail);
      assert(*tail);
      assert(! **tail);
      assert(sym);
      **tail = list_new(NULL);
      if (! **tail)
        return NULL;
      (**tail)->tag.type = TAG_PSYM;
      (**tail)->tag.data.psym = sym;
      *tail = &(**tail)->next.data.plist;
      return tail;
    }
    
    sw ekc3_buf_parse (s_buf *buf, p_ekc3 *dest)
    {
      s_do_block do_block;
      character c;
      sw r;
      sw result = 0;
      s_buf_save save;
      s_buf str_buf;
      s_list **tail;
      p_ekc3 tmp;
      assert(dest);
      assert(buf);
      if (! buf_init_alloc(&str_buf, BUF_SIZE)) {
        err_puts("ekc3_buf_parse: buf_init_alloc");
        assert(! "ekc3_buf_parse: buf_init_alloc");
        return -1;
      }
      ekc3_init(&tmp);
      tail = &tmp;
      buf_save_init(buf, &save);
      while (1) {
        r = buf_read_1(buf, "<%=");
        if (r < 0)
          break;
        if (r > 0) {
          result += r;
          if (! ekc3_append_and_empty_buf(&tail, &str_buf)) {
            err_puts("ekc3_buf_parse: ekc3_append_and_empty_buf 1");
            assert(! "ekc3_buf_parse: ekc3_append_and_empty_buf 1");
            goto ko;
          }
          r = ekc3_buf_parse_kc3_do_block(buf, &do_block);
          if (r <= 0) {
            err_puts("ekc3_buf_parse: ekc3_buf_parse_kc3_do_block");
            assert(! "ekc3_buf_parse: ekc3_buf_parse_kc3_do_block");
            goto restore;
          }
          if (! ekc3_append_do_block(&tail, &do_block)) {
            err_puts("ekc3_buf_parse: ekc3_append_do_block");
            assert(! "ekc3_buf_parse: ekc3_append_do_block");
            goto ko;
          }
          continue;
        }
        r = ekc3_buf_peek_kc3_silent_do_block(buf);
        if (r < 0) {
          err_puts("ekc3_buf_parse: ekc3_buf_peek_kc3_silent_do_block");
          assert(! "ekc3_buf_parse: ekc3_buf_peek_kc3_silent_do_block");
          goto ko;
        }
        if (r > 0) {
          if (! ekc3_append_and_empty_buf(&tail, &str_buf)) {
            err_puts("ekc3_buf_parse: ekc3_append_and_empty_buf 2");
            assert(! "ekc3_buf_parse: ekc3_append_and_empty_buf 2");
            goto ko;
          }
          r = ekc3_buf_parse_kc3_silent_do_block(buf, &do_block);
          if (r <= 0) {
            err_puts("ekc3_buf_parse: ekc3_buf_parse_kc3_silent_do_block");
            assert(! "ekc3_buf_parse: ekc3_buf_parse_kc3_silent_do_block");
            goto restore;
          }
          if (! ekc3_append_silent_do_block(&tail, &do_block)) {
            err_puts("ekc3_buf_parse: ekc3_append_silent_do_block");
            assert(! "ekc3_buf_parse: ekc3_append_silent_do_block");
            goto ko;
          }
          continue;
        }
        r = buf_read_character_utf8(buf, &c);
        if (r <= 0) {
          if (! ekc3_append_and_empty_buf(&tail, &str_buf)) {
            err_puts("ekc3_buf_parse: ekc3_append_and_empty_buf 3");
            assert(! "ekc3_buf_parse: ekc3_append_and_empty_buf 3");
            goto ko;
          }
          break;
        }
        result += r;
        r = buf_write_character_utf8(&str_buf, c);
        if (r <= 0) {
          err_puts("ekc3_buf_parse: buf_write_character_utf8 str_buf");
          assert(! "ekc3_buf_parse: buf_write_character_utf8 str_buf");
          goto restore;
        }
      }
      //err_inspect_list((const s_list * const *) &tmp);
      *dest = tmp;
      r = result;
      goto clean;
     ko:
      r = -1;
     restore:
      buf_save_restore_rpos(buf, &save);
      ekc3_clean(&tmp);
     clean:
      buf_clean(&str_buf);
      buf_save_clean(buf, &save);
      return r;
    }
    
    sw ekc3_buf_parse_kc3_do_block (s_buf *buf, s_do_block *dest)
    {
      sw r;
      sw result = 0;
      s_buf_save save;
      s_list *list;
      s_list **tail;
      s_do_block tmp;
      assert(buf);
      assert(dest);
      buf_save_init(buf, &save);
      list = NULL;
      tail = &list;
      while (1) {
        r = buf_ignore_spaces(buf);
        if (r < 0) {
          err_puts("ekc3_buf_parse_kc3_do_block: buf_ignore_spaces 1");
          assert(! "ekc3_buf_parse_kc3_do_block: buf_ignore_spaces 1");
          goto clean;
        }
        r = buf_read_1(buf, "%>");
        if (r < 0)
          goto clean;
        if (r > 0) {
          result += r;
          break;
        }
        *tail = list_new(NULL);
        r = buf_parse_tag(buf, &(*tail)->tag);
        if (r < 0) {
          list_delete_all(list);
          err_puts("ekc3_buf_parse_kc3_do_block: buf_parse_tag < 0");
          assert(! "ekc3_buf_parse_kc3_do_block: buf_parse_tag < 0");
          goto clean;
        }
        if (! r) {
          *tail = list_delete(*tail);
          err_puts("ekc3_buf_parse_kc3_do_block: buf_parse_tag = 0");
          assert(! "ekc3_buf_parse_kc3_do_block: buf_parse_tag = 0");
          break;
        }
        result += r;
        tail = &(*tail)->next.data.plist;
      }
      if (! do_block_init_from_list(&tmp, &list)) {
        list_delete_all(list);
        err_puts("ekc3_buf_parse_kc3_do_block: do_block_init_from_list");
        assert(! "ekc3_buf_parse_kc3_do_block: do_block_init_from_list");
        r = -1;
        goto clean;
      }
      list_delete_all(list);
      *dest = tmp;
      r = result;
     clean:
      buf_save_clean(buf, &save);
      return r;
    }
    
    sw ekc3_buf_parse_kc3_silent_do_block (s_buf *buf, s_do_block *dest)
    {
      sw r;
      sw result = 0;
      s_buf_save save;
      assert(buf);
      assert(dest);
      buf_save_init(buf, &save);
      r = buf_ignore_spaces(buf);
      if (r < 0) {
        err_puts("ekc3_buf_parse_kc3_silent_do_block: buf_ignore_spaces 1");
        assert(! "ekc3_buf_parse_kc3_silent_do_block: buf_ignore_spaces 1");
        goto clean;
      }
      r = buf_read_1(buf, "<%");
      if (r < 0) {
        err_puts("ekc3_buf_parse_kc3_silent_do_block: buf_read_1 < 0");
        assert(! "ekc3_buf_parse_kc3_silent_do_block: buf_read_1 < 0");
        goto restore;
      }
      if (! r) {
        err_puts("ekc3_buf_parse_kc3_silent_do_block: buf_read_1 = 0");
        assert(! "ekc3_buf_parse_kc3_silent_do_block: buf_read_1 = 0");
        goto restore;
      }
      result += r;
      r = buf_ignore_spaces(buf);
      if (r <= 0) {
        err_puts("ekc3_buf_parse_kc3_silent_do_block: buf_ignore_spaces 2");
        assert(! "ekc3_buf_parse_kc3_silent_do_block: buf_ignore_spaces 2");
        goto restore;
      }
      result += r;
      r = ekc3_buf_parse_kc3_do_block(buf, dest);
      if (r <= 0) {
        err_puts("ekc3_buf_parse_kc3_silent_do_block:"
                 " buf_parse_kc3_do_block");
        assert(!("ekc3_buf_parse_kc3_silent_do_block:"
                 " buf_parse_kc3_do_block"));
        goto restore;
      }
      result += r;
      r = result;
      goto clean;
     restore:
      buf_save_restore_rpos(buf, &save);
     clean:
      buf_save_clean(buf, &save);
      return r;
    }
    
    sw ekc3_buf_peek_kc3_silent_do_block (s_buf *buf)
    {
      sw r;
      sw result = 0;
      s_buf_save save;
      buf_save_init(buf, &save);
      r = buf_ignore_spaces(buf);
      if (r < 0) {
        r = 0;
        goto clean;
      }
      result += r;
      r = buf_read_1(buf, "<%");
      if (r <= 0)
        goto restore;
      result += r;
      r = buf_ignore_spaces(buf);
      if (r <= 0)
        goto restore;
      result += r;
      r = result;
     restore:
      buf_save_restore_rpos(buf, &save);
     clean:
      buf_save_clean(buf, &save);
      return r;
    }
    
    void ekc3_clean (p_ekc3 *ekc3)
    {
      list_delete_all(*ekc3);
    }
    
    bool ekc3_eval_silent_do_block (const s_do_block *do_block)
    {
      uw i = 0;
      s_tag result = {0};
      assert(do_block);
      while (i < do_block->count) {
        if (! eval_tag(do_block->tag + i, &result))
          return false;
        tag_clean(&result);
        i++;
      }
      return true;
    }
    
    p_ekc3 * ekc3_init (p_ekc3 *ekc3)
    {
      p_ekc3 tmp = {0};
      *ekc3 = tmp;
      return ekc3;
    }
    
    s_str * ekc3_inspect_do_block (const s_do_block *do_block, s_str *dest)
    {
      uw i = 0;
      s_tag result = {0};
      const s_sym *type;
      assert(do_block);
      while (i < do_block->count) {
        tag_clean(&result);
        if (! eval_tag(do_block->tag + i, &result))
          return NULL;
        i++;
      }
      switch (result.type) {
      case TAG_STR:
        if (! str_init_copy(dest, &result.data.str))
          return NULL;
        break;
      default:
        type = &g_sym_Str;
        if (! str_init_cast(dest, &type, &result))
          return NULL;
        break;
      }
      tag_clean(&result);
      return dest;
    }
    
    s_tag * ekc3_load (const s_str *path, s_tag *dest)
    {
      s_buf buf;
      s32 fd = -1;
      s_tag tmp = {0};
      if (! buf_init_alloc(&buf, BUF_SIZE))
        return NULL;
      if (! file_open_r(path, &fd))
        goto clean;
      if (! buf_fd_open_r(&buf, fd))
        goto clean;
      if (! ekc3_buf_parse(&buf, &tmp.data.plist))
        goto clean;
      tmp.type = TAG_PLIST;
      buf_clean(&buf);
      *dest = tmp;
      return dest;
     clean:
      if (fd > 0)
        close(fd);
      buf_clean(&buf);
      return NULL;
    }
    
    sw ekc3_render (s_buf *buf, const p_ekc3 *ekc3)
    {
      const s_list *l;
      sw r;
      sw result = 0;
      assert(ekc3);
      l = *ekc3;
      while (l) {
        if (l->tag.type == TAG_PSYM &&
            l->tag.data.psym == sym_1("silent")) {
          l = list_next(l);
          if (! l ||
              l->tag.type != TAG_DO_BLOCK) {
            err_puts("ekc3_render: :silent without a do_block");
            assert(! "ekc3_render: :silent without a do_block");
            return -1;
          }
          if (! ekc3_eval_silent_do_block(&l->tag.data.do_block)) {
            err_puts("ekc3_render: ekc3_eval_silent_do_block");
            assert(! "ekc3_render: ekc3_eval_silent_do_block");
            return -1;
          }
        }
        else {
          if ((r = ekc3_render_tag(buf, &l->tag)) < 0) {
            err_puts("ekc3_render: ekc3_render_tag");
            assert(! "ekc3_render: ekc3_render_tag");
            return r;
          }
          result += r;
        }
        l = list_next(l);
      }
      return result;
    }
    
    s_str * ekc3_render_buf_to_str (s_buf *in, s_str *dest)
    {
      p_ekc3 ekc3;
      sw r;
      assert(in);
      ekc3_init(&ekc3);
      r = ekc3_buf_parse(in, &ekc3);
      if (r < 0) {
        err_puts("ekc3_render_buf_to_str: ekc3_buf_parse");
        assert(! "ekc3_render_buf_to_str: ekc3_buf_parse");
        return NULL;
      }
      if (! ekc3_render_to_str(&ekc3, dest)) {
        ekc3_clean(&ekc3);
        return NULL;
      }
      ekc3_clean(&ekc3);
      return dest;
    }
    
    s_str * ekc3_render_file_to_str (const s_str *path, s_str *dest)
    {
      s_env *env;
      s_tag *file_dir;
      s_tag  file_dir_save;
      s_tag *file_path;
      s_tag  file_path_save;
      FILE *fp;
      s_buf in;
      char  in_data[BUF_SIZE];
      s_str *result;
      env = env_global();
      buf_init(&in, false, BUF_SIZE, in_data);
      fp = file_open(path->ptr.pchar, "rb");
      if (! fp)
        return NULL;
      if (! buf_file_open_r(&in, fp)) {
        fclose(fp);
        return NULL;
      }
      file_dir = frame_get_w(env->global_frame, &g_sym___DIR__);
      file_dir_save = *file_dir;
      file_dirname(path, &file_dir->data.str);
      file_path = frame_get_w(env->global_frame, &g_sym___FILE__);
      file_path_save = *file_path;
      file_path->data.str = *path;
      result = ekc3_render_buf_to_str(&in, dest);
      tag_clean(file_dir);
      *file_dir = file_dir_save;
      *file_path = file_path_save;
      buf_file_close(&in);
      fclose(fp);
      return result;
    }
    
    sw ekc3_render_raw_do_block (s_buf *buf, const s_do_block *do_block)
    {
      uw i;
      sw r;
      s_tag result = {0};
      assert(buf);
      assert(do_block);
      i = 1;
      while (i < do_block->count) {
        tag_clean(&result);
        if (! eval_tag(do_block->tag + i, &result))
          return -1;
        i++;
      }
      switch (result.type) {
      case TAG_STR:
        r = buf_write_str(buf, &result.data.str);
        break;
      case TAG_VOID:
        r = 0;
        break;
      default:
        r = buf_inspect_tag(buf, &result);
      }
      tag_clean(&result);
      return r;
    }
    
    sw ekc3_render_raw_do_block_size (s_pretty *pretty,
                                      const s_do_block *do_block)
    {
      uw i;
      sw r;
      s_tag result = {0};
      assert(pretty);
      assert(do_block);
      i = 1;
      while (i < do_block->count) {
        tag_clean(&result);
        if (! eval_tag(do_block->tag + i, &result))
          return -1;
        i++;
      }
      switch (result.type) {
      case TAG_STR:
        r = buf_write_str_size(pretty, &result.data.str);
        break;
      case TAG_VOID:
        r = 0;
        break;
      default:
        r = buf_inspect_tag_size(pretty, &result);
      }
      tag_clean(&result);
      return r;
    }
    
    sw ekc3_render_size (s_pretty *pretty, const p_ekc3 *ekc3)
    {
      const s_list *l;
      sw r;
      sw result = 0;
      assert(pretty);
      assert(ekc3);
      l = *ekc3;
      while (l) {
        if (l->tag.type == TAG_PSYM &&
            l->tag.data.psym == sym_1("silent")) {
          l = list_next(l);
          if (! l ||
              l->tag.type != TAG_DO_BLOCK) {
            err_puts("ekc3_render_size: :silent without a do_block");
            assert(! "ekc3_render_size: :silent without a do_block");
            return -1;
          }
          if (! ekc3_eval_silent_do_block(&l->tag.data.do_block)) {
            err_puts("ekc3_render_size: ekc3_eval_silent_do_block");
            assert(! "ekc3_render_size: ekc3_eval_silent_do_block");
            return -1;
          }
        }
        else {
          if ((r = ekc3_render_tag_size(pretty, &l->tag)) < 0) {
            err_puts("ekc3_render_size: ekc3_render_tag_size");
            assert(! "ekc3_render_size: ekc3_render_tag_size");
            return r;
          }
          result += r;
        }
        l = list_next(l);
      }
      return result;
    }
    
    sw ekc3_render_tag (s_buf *buf, const s_tag *tag)
    {
      const s_do_block *do_block;
      s_str escaped = {0};
      s_str in = {0};
      sw r;
      switch(tag->type) {
      case TAG_DO_BLOCK:
        do_block = &tag->data.do_block;
        if (do_block->count > 1 &&
            do_block->tag->type == TAG_IDENT &&
            do_block->tag->data.ident.sym == sym_1("raw")) {
          if ((r = ekc3_render_raw_do_block(buf, do_block)) < 0) {
            err_puts("ekc3_render_tag: ekc3_render_raw_do_block");
            assert(! "ekc3_render_tag: ekc3_render_raw_do_block");
          }
          return r;
        }
        if (! ekc3_inspect_do_block(&tag->data.do_block, &in)) {
          err_puts("ekc3_render_tag: ekc3_inspect_do_block");
          assert(! "ekc3_render_tag: ekc3_inspect_do_block");
          return -1;
        }
        if (! html_escape(&in, &escaped)) {
          str_clean(&in);
          err_puts("ekc3_render_tag: html_escape");
          assert(! "ekc3_render_tag: html_escape");
          return -1;
        }
        str_clean(&in);
        if ((r = buf_write_str(buf, &escaped)) < 0) {
          err_puts("ekc3_render_tag: buf_write_str 1");
          assert(! "ekc3_render_tag: buf_write_str 1");
        }
        str_clean(&escaped);
        return r;
      case TAG_STR:
        if ((r = buf_write_str(buf, &tag->data.str)) < 0) {
          err_puts("ekc3_render_tag: buf_write_str 2");
          assert(! "ekc3_render_tag: buf_write_str 2");
        }
        return r;
      default:
        break;
      }
      err_write_1("ekc3_render_tag: cannot render ");
      err_write_1(tag_type_to_string(tag->type));
      err_write_1(": ");
      err_inspect_tag(tag);
      err_write_1("\n");
      return -1;
    }
    
    sw ekc3_render_tag_size (s_pretty *pretty, const s_tag *tag)
    {
      const s_do_block *do_block;
      s_str escaped = {0};
      s_str in = {0};
      sw r;
      switch(tag->type) {
      case TAG_DO_BLOCK:
        do_block = &tag->data.do_block;
        if (do_block->count > 1 &&
            do_block->tag->type == TAG_IDENT &&
            do_block->tag->data.ident.sym == sym_1("raw")) {
          if ((r = ekc3_render_raw_do_block_size(pretty, do_block)) < 0) {
            err_puts("ekc3_render_tag_size: ekc3_render_raw_do_block");
            assert(! "ekc3_render_tag_size: ekc3_render_raw_do_block");
          }
          return r;
        }
        if (! ekc3_inspect_do_block(&tag->data.do_block, &in)) {
          err_puts("ekc3_render_tag_size: ekc3_inspect_do_block");
          assert(! "ekc3_render_tag_size: ekc3_inspect_do_block");
          return -1;
        }
        if (! html_escape(&in, &escaped)) {
          str_clean(&in);
          err_puts("ekc3_render_tag_size: html_escape");
          assert(! "ekc3_render_tag_size: html_escape");
          return -1;
        }
        str_clean(&in);
        if ((r = buf_write_str_size(pretty, &escaped)) < 0) {
          err_puts("ekc3_render_tag_size: buf_write_str 1");
          assert(! "ekc3_render_tag_size: buf_write_str 1");
        }
        str_clean(&escaped);
        return r;
      case TAG_STR:
        if ((r = buf_write_str_size(pretty, &tag->data.str)) < 0) {
          err_puts("ekc3_render_tag_size: buf_write_str 2");
          assert(! "ekc3_render_tag_size: buf_write_str 2");
        }
        return r;
      default:
        break;
      }
      err_write_1("ekc3_render_tag_size: cannot render ");
      err_write_1(tag_type_to_string(tag->type));
      err_write_1(": ");
      err_inspect_tag(tag);
      err_write_1("\n");
      return -1;
    }
    
    s_str * ekc3_render_to_str (const p_ekc3 *ekc3, s_str *dest)
    {
      
      s_buf out;
      s_pretty pretty = {0};
      sw r;
      assert(ekc3);
      assert(dest);
      if ((r = ekc3_render_size(&pretty, ekc3)) < 0)
        return NULL;
      if (! buf_init_alloc(&out, r))
        return NULL;
      if ((r = ekc3_render(&out, ekc3)) < 0) {
        buf_clean(&out);
        return NULL;
      }
      buf_to_str(&out, dest);
      return dest;
    }
    */