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 <libkc3/kc3.h>
    #include <unistd.h>
    #include "ekc3.h"
    #include "html.h"
    
    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)) {
        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.list;
      return tail;
    }
    
    s_list *** ekc3_append_block (s_list ***tail, s_block *block)
    {
      assert(tail);
      assert(*tail);
      assert(! **tail);
      assert(block);
      **tail = list_new(NULL);
      if (! **tail)
        return NULL;
      (**tail)->tag.type = TAG_BLOCK;
      (**tail)->tag.data.block = *block;
      *tail = &(**tail)->next.data.list;
      return tail;
    }
    
    s_list *** ekc3_append_silent_block (s_list ***tail, s_block *block)
    {
      assert(tail);
      assert(*tail);
      assert(! **tail);
      assert(block);
      if (! ekc3_append_sym(tail, sym_1("silent")))
        return NULL;
      if (! ekc3_append_block(tail, 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.list;
      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_SYM;
      (**tail)->tag.data.sym = sym;
      *tail = &(**tail)->next.data.list;
      return tail;
    }
    
    sw ekc3_buf_parse (s_buf *buf, p_ekc3 *dest)
    {
      s_block 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_block(buf, &block);
          if (r <= 0) {
            err_puts("ekc3_buf_parse: ekc3_buf_parse_kc3_block");
            assert(! "ekc3_buf_parse: ekc3_buf_parse_kc3_block");
            goto restore;
          }
          if (! ekc3_append_block(&tail, &block)) {
            err_puts("ekc3_buf_parse: ekc3_append_block");
            assert(! "ekc3_buf_parse: ekc3_append_block");
            goto ko;
          }
          continue;
        }
        r = ekc3_buf_peek_kc3_silent_block(buf);
        if (r < 0) {
          err_puts("ekc3_buf_parse: ekc3_buf_peek_kc3_silent_block");
          assert(! "ekc3_buf_parse: ekc3_buf_peek_kc3_silent_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_block(buf, &block);
          if (r <= 0) {
            err_puts("ekc3_buf_parse: ekc3_buf_parse_kc3_silent_block");
            assert(! "ekc3_buf_parse: ekc3_buf_parse_kc3_silent_block");
            goto restore;
          }
          if (! ekc3_append_silent_block(&tail, &block)) {
            err_puts("ekc3_buf_parse: ekc3_append_silent_block");
            assert(! "ekc3_buf_parse: ekc3_append_silent_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_block (s_buf *buf, s_block *dest)
    {
      sw r;
      sw result = 0;
      s_buf_save save;
      s_list *list;
      s_list **tail;
      s_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_block: buf_ignore_spaces 1");
          assert(! "ekc3_buf_parse_kc3_block: buf_ignore_spaces 1");
          goto clean;
        }
        /*
        if (! r) {
          err_puts("ekc3_buf_parse_kc3_block: buf_ignore_spaces 2");
          assert(! "ekc3_buf_parse_kc3_block: buf_ignore_spaces 2");
          r = -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_block: buf_parse_tag < 0");
          assert(! "ekc3_buf_parse_kc3_block: buf_parse_tag < 0");
          goto clean;
        }
        if (! r) {
          *tail = list_delete(*tail);
          err_puts("ekc3_buf_parse_kc3_block: buf_parse_tag = 0");
          assert(! "ekc3_buf_parse_kc3_block: buf_parse_tag = 0");
          break;
        }
        result += r;
        tail = &(*tail)->next.data.list;
      }
      if (! block_init_from_list(&tmp, &list)) {
        list_delete_all(list);
        err_puts("ekc3_buf_parse_kc3_block: block_init_from_list");
        assert(! "ekc3_buf_parse_kc3_block: 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_block (s_buf *buf, s_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_block: buf_ignore_spaces 1");
        assert(! "ekc3_buf_parse_kc3_silent_block: buf_ignore_spaces 1");
        goto clean;
      }
      r = buf_read_1(buf, "<%");
      if (r < 0) {
        err_puts("ekc3_buf_parse_kc3_silent_block: buf_read_1 < 0");
        assert(! "ekc3_buf_parse_kc3_silent_block: buf_read_1 < 0");
        goto restore;
      }
      if (! r) {
        err_puts("ekc3_buf_parse_kc3_silent_block: buf_read_1 = 0");
        assert(! "ekc3_buf_parse_kc3_silent_block: buf_read_1 = 0");
        goto restore;
      }
      result += r;
      r = buf_ignore_spaces(buf);
      if (r <= 0) {
        err_puts("ekc3_buf_parse_kc3_silent_block: buf_ignore_spaces 2");
        assert(! "ekc3_buf_parse_kc3_silent_block: buf_ignore_spaces 2");
        goto restore;
      }
      result += r;
      r = ekc3_buf_parse_kc3_block(buf, dest);
      if (r <= 0) {
        err_puts("ekc3_buf_parse_kc3_silent_block: buf_parse_kc3_block");
        assert(! "ekc3_buf_parse_kc3_silent_block: buf_parse_kc3_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_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_block (const s_block *block)
    {
      uw i = 0;
      s_tag result = {0};
      assert(block);
      while (i < block->count) {
        if (! eval_tag(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_block (const s_block *block, s_str *dest)
    {
      uw i = 0;
      s_tag result = {0};
      const s_sym *type;
      assert(block);
      while (i < block->count) {
        tag_clean(&result);
        if (! eval_tag(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.list))
        goto clean;
      tmp.type = TAG_LIST;
      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_SYM &&
            l->tag.data.sym == sym_1("silent")) {
          l = list_next(l);
          if (! l ||
              l->tag.type != TAG_BLOCK) {
            err_puts("ekc3_render: :silent without a block");
            assert(! "ekc3_render: :silent without a block");
            return -1;
          }
          if (! ekc3_eval_silent_block(&l->tag.data.block)) {
            err_puts("ekc3_render: ekc3_eval_silent_block");
            assert(! "ekc3_render: ekc3_eval_silent_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_block (s_buf *buf, const s_block *block)
    {
      uw i;
      sw r;
      s_tag result = {0};
      assert(buf);
      assert(block);
      i = 1;
      while (i < block->count) {
        tag_clean(&result);
        if (! eval_tag(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_block_size (s_pretty *pretty, const s_block *block)
    {
      uw i;
      sw r;
      s_tag result = {0};
      assert(pretty);
      assert(block);
      i = 1;
      while (i < block->count) {
        tag_clean(&result);
        if (! eval_tag(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_SYM &&
            l->tag.data.sym == sym_1("silent")) {
          l = list_next(l);
          if (! l ||
              l->tag.type != TAG_BLOCK) {
            err_puts("ekc3_render_size: :silent without a block");
            assert(! "ekc3_render_size: :silent without a block");
            return -1;
          }
          if (! ekc3_eval_silent_block(&l->tag.data.block)) {
            err_puts("ekc3_render_size: ekc3_eval_silent_block");
            assert(! "ekc3_render_size: ekc3_eval_silent_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_block *block;
      s_str escaped = {0};
      s_str in = {0};
      sw r;
      switch(tag->type) {
      case TAG_BLOCK:
        block = &tag->data.block;
        if (block->count > 1 &&
            block->tag->type == TAG_IDENT &&
            block->tag->data.ident.sym == sym_1("raw")) {
          if ((r = ekc3_render_raw_block(buf, block)) < 0) {
            err_puts("ekc3_render_tag: ekc3_render_raw_block");
            assert(! "ekc3_render_tag: ekc3_render_raw_block");
          }
          return r;
        }
        if (! ekc3_inspect_block(&tag->data.block, &in)) {
          err_puts("ekc3_render_tag: ekc3_inspect_block");
          assert(! "ekc3_render_tag: ekc3_inspect_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_block *block;
      s_str escaped = {0};
      s_str in = {0};
      sw r;
      switch(tag->type) {
      case TAG_BLOCK:
        block = &tag->data.block;
        if (block->count > 1 &&
            block->tag->type == TAG_IDENT &&
            block->tag->data.ident.sym == sym_1("raw")) {
          if ((r = ekc3_render_raw_block_size(pretty, block)) < 0) {
            err_puts("ekc3_render_tag_size: ekc3_render_raw_block");
            assert(! "ekc3_render_tag_size: ekc3_render_raw_block");
          }
          return r;
        }
        if (! ekc3_inspect_block(&tag->data.block, &in)) {
          err_puts("ekc3_render_tag_size: ekc3_inspect_block");
          assert(! "ekc3_render_tag_size: ekc3_inspect_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;
    }