#include <assert.h>
#include <limits.h>

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "eo_parser.h"
#include "eolian_priv.h"

#define CASE_LOCK(ls, var, msg) \
   if (has_##var) \
     eo_lexer_syntax_error(ls, "double " msg); \
   has_##var = EINA_TRUE;

#define FILL_BASE(exp, ls, l, c, tp) \
   (exp).unit = ls->unit; \
   (exp).file = eina_stringshare_ref(ls->filename); \
   (exp).line = l; \
   (exp).column = c; \
   (exp).type = EOLIAN_OBJECT_##tp

#define FILL_DOC(ls, def, docf) \
   if (ls->t.token == TOK_DOC) \
     { \
        def->docf = ls->t.value.doc; \
        ls->t.value.doc = NULL; \
        eo_lexer_get(ls); \
     }

static void
error_expected(Eo_Lexer *ls, int token)
{
   char  buf[256 + 128];
   char tbuf[256];
   eo_lexer_token_to_str(token, tbuf);
   snprintf(buf, sizeof(buf), "'%s' expected", tbuf);
   eo_lexer_syntax_error(ls, buf);
}

static Eina_Bool
test_next(Eo_Lexer *ls, int token)
{
   if (ls->t.token == token)
     {
        eo_lexer_get(ls);
        return EINA_TRUE;
     }
   return EINA_FALSE;
}

static void
check(Eo_Lexer *ls, int token)
{
   if (ls->t.token != token)
     error_expected(ls, token);
}

static void
check_kw(Eo_Lexer *ls, int kw)
{
   if (ls->t.kw != kw)
     error_expected(ls, TOK_VALUE + kw);
}

static void
check_next(Eo_Lexer *ls, int token)
{
   check(ls, token);
   eo_lexer_get(ls);
}

static void
check_kw_next(Eo_Lexer *ls, int kw)
{
   check_kw(ls, kw);
   eo_lexer_get(ls);
}

static void
check_match(Eo_Lexer *ls, int what, int who, int where, int col)
{
   if (!test_next(ls, what))
     {
        if (where == ls->line_number)
          error_expected(ls, what);
        else
          {
             char  buf[256 + 256 + 128];
             char tbuf[256];
             char vbuf[256];
             eo_lexer_token_to_str(what, tbuf);
             eo_lexer_token_to_str(who , vbuf);
             snprintf(buf, sizeof(buf),
                      "'%s' expected (to close '%s' at line %d, column %d)",
                      tbuf, vbuf, where, col);
             eo_lexer_syntax_error(ls, buf);
          }
     }
}

static Eina_Stringshare *
make_c_name(const char *fulln)
{
   char *mbuf = strdup(fulln);
   for (char *p = mbuf; (p = strchr(p, '.')); ++p)
     *p = '_';
   Eina_Stringshare *ret = eina_stringshare_add(mbuf);
   free(mbuf);
   return ret;
}

static Eina_Bool
compare_class_file(const char *fn1, const char *fn2)
{
   return !strcmp(fn1, fn2);
}


static Eolian_Object *
_eolian_decl_get(Eo_Lexer *ls, const char *name)
{
   Eolian_Object *obj = eina_hash_find(ls->state->main.unit.objects, name);
   if (!obj)
     obj = eina_hash_find(ls->state->staging.unit.objects, name);
   if (obj && ((obj->type == EOLIAN_OBJECT_CLASS) ||
               (obj->type == EOLIAN_OBJECT_TYPEDECL) ||
               (obj->type == EOLIAN_OBJECT_CONSTANT)))
     return obj;

   return NULL;
}

static const char *
_eolian_decl_name_get(Eolian_Object *obj)
{
   switch (obj->type)
     {
      case EOLIAN_OBJECT_CLASS:
        return "class";
      case EOLIAN_OBJECT_TYPEDECL:
        switch (((Eolian_Typedecl *)obj)->type)
          {
           case EOLIAN_TYPEDECL_ALIAS:
             return "type alias";
           case EOLIAN_TYPEDECL_STRUCT:
           case EOLIAN_TYPEDECL_STRUCT_OPAQUE:
             return "struct";
           case EOLIAN_TYPEDECL_ENUM:
             return "enum";
           default:
             break;
          }
        goto end;
      case EOLIAN_OBJECT_CONSTANT:
        return "constant";
      default:
        break;
     }
end:
   return "unknown";
}

static void
redef_error(Eo_Lexer *ls, Eolian_Object *obj, Eolian_Object *nobj)
{
   char buf[256 + 128], fbuf[256] = { '\0' };
   if (ls->filename != obj->file)
     snprintf(fbuf, sizeof(fbuf), "%s:%d:%d", obj->file, obj->line, obj->column);
   else
     snprintf(fbuf, sizeof(fbuf), "%d:%d", obj->line, obj->column);

   if (nobj->type != obj->type)
     snprintf(buf, sizeof(buf), "%s '%s' redefined as %s (originally at %s)",
              _eolian_decl_name_get(obj), obj->name,
              _eolian_decl_name_get(nobj), fbuf);
   else
     snprintf(buf, sizeof(buf), "%s '%s' redefined (originally at %s)",
              _eolian_decl_name_get(obj), obj->name, fbuf);

   eo_lexer_syntax_error(ls, buf);
}

static Eina_Strbuf *
parse_name(Eo_Lexer *ls, Eina_Strbuf *buf)
{
   check(ls, TOK_VALUE);
   if (eo_lexer_is_type_keyword(ls->t.kw))
     eo_lexer_syntax_error(ls, "invalid name");
   eina_strbuf_reset(buf);
   for (;;)
     {
        eina_strbuf_append(buf, ls->t.value.s);
        eo_lexer_get(ls);
        if (ls->t.token != '.') break;
        eo_lexer_get(ls);
        eina_strbuf_append(buf, ".");
        check(ls, TOK_VALUE);
        if (eo_lexer_is_type_keyword(ls->t.kw))
          eo_lexer_syntax_error(ls, "invalid name");
     }
   return buf;
}

static Eina_Stringshare *
parse_c_name(Eo_Lexer *ls)
{
   eo_lexer_get(ls);
   int pline = ls->line_number, pcol = ls->column;
   check_next(ls, '(');
   check(ls, TOK_VALUE);
   if (eo_lexer_is_type_keyword(ls->t.kw))
     eo_lexer_syntax_error(ls, "invalid name");
   Eina_Stringshare *cname = eina_stringshare_add(ls->t.value.s);
   eo_lexer_get(ls);
   if (ls->t.token != ')')
     {
        eina_stringshare_del(cname);
        check_match(ls, ')', '(', pline, pcol);
        return NULL; /* unreachable */
     }
   eo_lexer_get(ls);
   return cname;
}

static Eolian_Binary_Operator
get_binop_id(int tok)
{
   switch (tok)
     {
      case '+': return EOLIAN_BINOP_ADD;
      case '-': return EOLIAN_BINOP_SUB;
      case '*': return EOLIAN_BINOP_MUL;
      case '/': return EOLIAN_BINOP_DIV;
      case '%': return EOLIAN_BINOP_MOD;

      case TOK_EQ: return EOLIAN_BINOP_EQ;
      case TOK_NQ: return EOLIAN_BINOP_NQ;
      case '>'   : return EOLIAN_BINOP_GT;
      case '<'   : return EOLIAN_BINOP_LT;
      case TOK_GE: return EOLIAN_BINOP_GE;
      case TOK_LE: return EOLIAN_BINOP_LE;

      case TOK_AND: return EOLIAN_BINOP_AND;
      case TOK_OR : return EOLIAN_BINOP_OR;

      case '&': return EOLIAN_BINOP_BAND;
      case '|': return EOLIAN_BINOP_BOR;
      case '^': return EOLIAN_BINOP_BXOR;

      case TOK_LSH: return EOLIAN_BINOP_LSH;
      case TOK_RSH: return EOLIAN_BINOP_RSH;

      default: return EOLIAN_BINOP_INVALID;
     }
}

static Eolian_Unary_Operator
get_unop_id(int tok)
{
   switch (tok)
     {
      case '-': return EOLIAN_UNOP_UNM;
      case '+': return EOLIAN_UNOP_UNP;
      case '!': return EOLIAN_UNOP_NOT;
      case '~': return EOLIAN_UNOP_BNOT;

      default: return EOLIAN_UNOP_INVALID;
     }
}

static const int binprec[] = {
   -1, /* invalid */

   8, /* + */
   8, /* - */
   9, /* * */
   9, /* / */
   9, /* % */

   3, /* == */
   3, /* != */
   3, /* >  */
   3, /* <  */
   3, /* >= */
   3, /* <= */

   2, /* && */
   1, /* || */

   6, /* &  */
   4, /* |  */
   5, /* ^  */
   7, /* << */
   7  /* >> */
};

#define UNARY_PRECEDENCE 10

static Eolian_Expression *parse_expr_bin(Eo_Lexer *ls, int min_prec);
static Eolian_Expression *parse_expr(Eo_Lexer *ls);

static Eolian_Expression *
parse_expr_simple(Eo_Lexer *ls)
{
   Eolian_Expression *expr;
   Eolian_Unary_Operator unop = get_unop_id(ls->t.token);
   if (unop != EOLIAN_UNOP_INVALID)
     {
        int line = ls->line_number, col = ls->column;
        eo_lexer_get(ls);
        Eolian_Expression *exp = parse_expr_bin(ls, UNARY_PRECEDENCE);
        expr = eo_lexer_expr_new(ls);
        FILL_BASE(expr->base, ls, line, col, EXPRESSION);
        expr->unop = unop;
        expr->type = EOLIAN_EXPR_UNARY;
        expr->expr = exp;
        eo_lexer_expr_release_ref(ls, exp);
        return expr;
     }
   switch (ls->t.token)
     {
      case TOK_NUMBER:
        {
           int line = ls->line_number, col = ls->column;
           expr = eo_lexer_expr_new(ls);
           FILL_BASE(expr->base, ls, line, col, EXPRESSION);
           expr->type = ls->t.kw + 1; /* map Numbers from lexer to expr type */
           memcpy(&expr->value, &ls->t.value, sizeof(expr->value));
           eo_lexer_get(ls);
           break;
        }
      case TOK_STRING:
        {
           int line = ls->line_number, col = ls->column;
           expr = eo_lexer_expr_new(ls);
           FILL_BASE(expr->base, ls, line, col, EXPRESSION);
           expr->type = EOLIAN_EXPR_STRING;
           expr->value.s = eina_stringshare_ref(ls->t.value.s);
           eo_lexer_get(ls);
           break;
        }
      case TOK_CHAR:
        {
           int line = ls->line_number, col = ls->column;
           expr = eo_lexer_expr_new(ls);
           FILL_BASE(expr->base, ls, line, col, EXPRESSION);
           expr->type = EOLIAN_EXPR_CHAR;
           expr->value.c = ls->t.value.c;
           eo_lexer_get(ls);
           break;
        }
      case TOK_VALUE:
        {
           int line = ls->line_number, col = ls->column;
           switch (ls->t.kw)
             {
              case KW_true:
              case KW_false:
                {
                   expr = eo_lexer_expr_new(ls);
                   expr->type = EOLIAN_EXPR_BOOL;
                   expr->value.b = (ls->t.kw == KW_true);
                   eo_lexer_get(ls);
                   break;
                }
              case KW_null:
                {
                   expr = eo_lexer_expr_new(ls);
                   expr->type = EOLIAN_EXPR_NULL;
                   eo_lexer_get(ls);
                   break;
                }
              default:
                {
                   Eina_Strbuf *buf = eina_strbuf_new();
                   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
                   expr = eo_lexer_expr_new(ls);
                   expr->type = EOLIAN_EXPR_NAME;
                   parse_name(ls, buf);
                   expr->value.s = eina_stringshare_add(eina_strbuf_string_get
                       (buf));
                   eo_lexer_dtor_pop(ls);
                   break;
                }
             }
           FILL_BASE(expr->base, ls, line, col, EXPRESSION);
           break;
        }
      case '(':
        {
           int line = ls->line_number, col = ls->column;
           eo_lexer_get(ls);
           expr = parse_expr(ls);
           check_match(ls, ')', '(', line, col);
           break;
        }
      default:
        expr = NULL; /* shut up compiler */
        eo_lexer_syntax_error(ls, "unexpected symbol");
        break;
     }

   return expr;
}

static Eolian_Expression *
parse_expr_bin(Eo_Lexer *ls, int min_prec)
{
   int line = ls->line_number, col = ls->column;
   Eolian_Expression *lhs = parse_expr_simple(ls);
   for (;;)
     {
        Eolian_Expression *rhs, *bin;
        Eolian_Binary_Operator op = get_binop_id(ls->t.token);
        int prec = binprec[op];
        if ((op == EOLIAN_BINOP_INVALID) || (prec < 0) || (prec < min_prec))
          break;
        eo_lexer_get(ls);
        rhs = parse_expr_bin(ls, prec + 1);
        bin = eo_lexer_expr_new(ls);
        FILL_BASE(bin->base, ls, line, col, EXPRESSION);
        bin->binop = op;
        bin->type = EOLIAN_EXPR_BINARY;
        bin->lhs = lhs;
        eo_lexer_expr_release_ref(ls, lhs);
        bin->rhs = rhs;
        eo_lexer_expr_release_ref(ls, rhs);
        lhs = bin;
     }
   return lhs;
}

static Eolian_Expression *
parse_expr(Eo_Lexer *ls)
{
   return parse_expr_bin(ls, 1);
}

static Eolian_Type *parse_type_void(Eo_Lexer *ls, Eina_Bool allow_ptr, Eina_Bool allow_const);

static Eolian_Type *
parse_type(Eo_Lexer *ls, Eina_Bool allow_ptr, Eina_Bool allow_const)
{
   Eolian_Type *ret;
   eo_lexer_context_push(ls);
   ret = parse_type_void(ls, allow_ptr, allow_const);
   if (ret->type == EOLIAN_TYPE_VOID)
     {
        eo_lexer_context_restore(ls);
        eo_lexer_syntax_error(ls, "non-void type expected");
     }
   eo_lexer_context_pop(ls);
   return ret;
}

static void
_struct_field_free(Eolian_Struct_Type_Field *def)
{
   eina_stringshare_del(def->base.file);
   eina_stringshare_del(def->base.name);
   database_type_del(def->type);
   database_doc_del(def->doc);
   free(def);
}

static Eolian_Typedecl *
parse_struct(Eo_Lexer *ls, const char *name, Eina_Bool is_extern,
             Eina_Bool is_beta, int line, int column, const char *freefunc,
             const char *cname)
{
   int bline = ls->line_number, bcolumn = ls->column;
   Eolian_Typedecl *def = eo_lexer_typedecl_new(ls);
   def->is_extern = is_extern;
   def->base.is_beta = is_beta;
   def->base.name = name;
   def->type = EOLIAN_TYPEDECL_STRUCT;
   def->fields = eina_hash_string_small_new(EINA_FREE_CB(_struct_field_free));
   if (freefunc)
     {
        def->freefunc = eina_stringshare_ref(freefunc);
        def->ownable = EINA_TRUE;
     }
   if (cname)
     def->base.c_name = eina_stringshare_ref(cname);
   else
     def->base.c_name = make_c_name(name);
   /* we can't know the order, pop when both are filled */
   if (freefunc && cname)
     {
        eo_lexer_dtor_pop(ls);
        eo_lexer_dtor_pop(ls);
     }
   else if (freefunc || cname)
     eo_lexer_dtor_pop(ls);
   check_next(ls, '{');
   FILL_DOC(ls, def, doc);
   while (ls->t.token != '}')
     {
        const char *fname;
        Eolian_Struct_Type_Field *fdef;
        Eolian_Type *tp;
        int fline = ls->line_number, fcol = ls->column;
        check(ls, TOK_VALUE);
        if (eina_hash_find(def->fields, ls->t.value.s))
          eo_lexer_syntax_error(ls, "double field definition");
        fdef = calloc(1, sizeof(Eolian_Struct_Type_Field));
        fname = eina_stringshare_ref(ls->t.value.s);
        eina_hash_add(def->fields, fname, fdef);
        def->field_list = eina_list_append(def->field_list, fdef);
        eolian_object_ref(&fdef->base);
        eo_lexer_get(ls);
        check_next(ls, ':');
        tp = parse_type(ls, EINA_TRUE, EINA_TRUE);
        FILL_BASE(fdef->base, ls, fline, fcol, STRUCT_FIELD);
        fdef->type = eo_lexer_type_release(ls, tp);
        fdef->base.name = eina_stringshare_ref(fname);
        Eina_Bool has_move = EINA_FALSE, has_by_ref = EINA_FALSE;
        for (;;) switch (ls->t.kw)
          {
           case KW_at_by_ref:
             CASE_LOCK(ls, by_ref, "by_ref qualifier");
             fdef->by_ref = EINA_TRUE;
             eo_lexer_get(ls);
             break;
           case KW_at_move:
             CASE_LOCK(ls, move, "move qualifier");
             fdef->move = EINA_TRUE;
             eo_lexer_get(ls);
             break;
           default:
             goto qual_end;
          }
qual_end:
        check_next(ls, ';');
        FILL_DOC(ls, fdef, doc);
        if (def->doc && fdef->doc && def->doc->since && !fdef->doc->since)
          fdef->doc->since = eina_stringshare_ref (def->doc->since);
     }
   check_match(ls, '}', '{', bline, bcolumn);
   FILL_BASE(def->base, ls, line, column, TYPEDECL);
   database_struct_add(ls->unit, eo_lexer_typedecl_release(ls, def));
   return def;
}

static void
_enum_field_free(Eolian_Enum_Type_Field *def)
{
   eina_stringshare_del(def->base.file);
   eina_stringshare_del(def->base.name);
   database_expr_del(def->value);
   database_doc_del(def->doc);
   free(def);
}

static Eolian_Typedecl *
parse_enum(Eo_Lexer *ls, const char *name, Eina_Bool is_extern,
           Eina_Bool is_beta, int line, int column, const char *cname)
{
   int bline = ls->line_number, bcolumn = ls->column;
   Eolian_Typedecl *def = eo_lexer_typedecl_new(ls);
   def->is_extern = is_extern;
   def->base.is_beta = is_beta;
   def->base.name = name;
   if (cname)
     {
        def->base.c_name = eina_stringshare_ref(cname);
        eo_lexer_dtor_pop(ls);
     }
   else
     def->base.c_name = make_c_name(name);
   def->type = EOLIAN_TYPEDECL_ENUM;
   def->fields = eina_hash_string_small_new(EINA_FREE_CB(_enum_field_free));
   check_next(ls, '{');
   FILL_DOC(ls, def, doc);
   if (ls->t.token == TOK_VALUE && ls->t.kw == KW_legacy)
     {
         if (eo_lexer_lookahead(ls) == ':')
           {
              /* consume keyword */
              eo_lexer_get(ls);
              /* consume colon */
              eo_lexer_get(ls);
              check(ls, TOK_VALUE);
              def->legacy = eina_stringshare_ref(ls->t.value.s);
              eo_lexer_get(ls);
              check_next(ls, ';');
           }
     }
   Eolian_Enum_Type_Field *prev_fl = NULL;
   int fl_nadd = 0;
   for (;;)
     {
        const char *fname;
        Eolian_Enum_Type_Field *fdef;
        int fline = ls->line_number, fcol = ls->column;
        check(ls, TOK_VALUE);
        if (eina_hash_find(def->fields, ls->t.value.s))
          eo_lexer_syntax_error(ls, "double field definition");
        fdef = calloc(1, sizeof(Eolian_Enum_Type_Field));
        fname = eina_stringshare_ref(ls->t.value.s);
        eina_hash_add(def->fields, fname, fdef);
        def->field_list = eina_list_append(def->field_list, fdef);
        eolian_object_ref(&fdef->base);
        eo_lexer_get(ls);
        FILL_BASE(fdef->base, ls, fline, fcol, ENUM_FIELD);
        fdef->base_enum = def;
        fdef->base.name = eina_stringshare_ref(fname);
        if (ls->t.token != '=')
          {
             if (!prev_fl)
               {
                  Eolian_Expression *eexp = eo_lexer_expr_new(ls);
                  FILL_BASE(eexp->base, ls, -1, -1, EXPRESSION);
                  eexp->type = EOLIAN_EXPR_INT;
                  eexp->value.i = 0;
                  fdef->value = eexp;
                  fdef->is_public_value = EINA_TRUE;
                  eo_lexer_expr_release_ref(ls, eexp);
                  prev_fl = fdef;
                  fl_nadd = 0;
               }
             else
               {
                  Eolian_Expression *rhs = eo_lexer_expr_new(ls),
                                    *bin = eo_lexer_expr_new(ls);
                  FILL_BASE(rhs->base, ls, -1, -1, EXPRESSION);
                  FILL_BASE(bin->base, ls, -1, -1, EXPRESSION);

                  rhs->type = EOLIAN_EXPR_INT;
                  rhs->value.i = ++fl_nadd;

                  bin->type = EOLIAN_EXPR_BINARY;
                  bin->binop = EOLIAN_BINOP_ADD;
                  bin->lhs = prev_fl->value;
                  bin->rhs = rhs;
                  bin->weak_lhs = EINA_TRUE;
                  eo_lexer_expr_release_ref(ls, rhs);

                  fdef->value = bin;
                  eo_lexer_expr_release_ref(ls, bin);
               }
          }
        else
          {
             ls->expr_mode = EINA_TRUE;
             eo_lexer_get(ls);
             fdef->value = parse_expr(ls);
             fdef->is_public_value = EINA_TRUE;
             ls->expr_mode = EINA_FALSE;
             prev_fl = fdef;
             fl_nadd = 0;
             eo_lexer_expr_release_ref(ls, fdef->value);
          }
        Eina_Bool want_next = (ls->t.token == ',');
        if (want_next)
          eo_lexer_get(ls);
        FILL_DOC(ls, fdef, doc);
        if (def->doc && fdef->doc && def->doc->since && !fdef->doc->since)
          fdef->doc->since = eina_stringshare_ref (def->doc->since);
        if (!want_next || ls->t.token == '}')
          break;
     }
   check_match(ls, '}', '{', bline, bcolumn);
   FILL_BASE(def->base, ls, line, column, TYPEDECL);
   database_enum_add(ls->unit, eo_lexer_typedecl_release(ls, def));
   return def;
}

/* error(Error1, Error2, Error3, ...) */
static Eolian_Type *
parse_type_error(Eo_Lexer *ls)
{
   Eolian_Type *def = eo_lexer_type_new(ls);
   Eina_Strbuf *buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   for (Eolian_Type *cdef = def;; cdef = cdef->next_type)
     {
        FILL_BASE(cdef->base, ls, ls->line_number, ls->column, TYPE);
        parse_name(ls, buf);
        cdef->type = EOLIAN_TYPE_ERROR;
        cdef->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
        eina_strbuf_reset(buf);
        if (ls->t.token != ',')
          break;
        eo_lexer_get(ls);
        cdef->next_type = eo_lexer_type_release(ls, eo_lexer_type_new(ls));
     }
   eo_lexer_dtor_pop(ls);
   return def;
}

static Eolian_Type *
parse_type_void(Eo_Lexer *ls, Eina_Bool allow_ptr, Eina_Bool allow_const)
{
   Eolian_Type *def;
   Eina_Strbuf *buf;
   int line = ls->line_number, col = ls->column;
   switch (ls->t.kw)
     {
      case KW_const:
        {
           if (!allow_const)
             break;
           int pline, pcol;
           eo_lexer_get(ls);
           pline = ls->line_number;
           pcol = ls->column;
           check_next(ls, '(');
           def = parse_type_void(ls, allow_ptr, EINA_FALSE);
           FILL_BASE(def->base, ls, line, col, TYPE);
           def->is_const = EINA_TRUE;
           check_match(ls, ')', '(', pline, pcol);
           return def;
        }
      case KW_ptr:
        {
           if (!allow_ptr)
             break;
           int pline, pcol;
           eo_lexer_get(ls);
           pline = ls->line_number;
           pcol = ls->column;
           check_next(ls, '(');
           def = parse_type_void(ls, EINA_FALSE, allow_const);
           FILL_BASE(def->base, ls, line, col, TYPE);
           def->is_ptr = EINA_TRUE;
           check_match(ls, ')', '(', pline, pcol);
           return def;
        }
      case KW_error:
        {
           int pline, pcolumn;
           eo_lexer_get(ls);
           pline = ls->line_number;
           pcolumn = ls->column;
           check_next(ls, '(');
           def = parse_type_error(ls);
           check_match(ls, ')', '(', pline, pcolumn);
           return def;
        }
      default:
        break;
     }
   def = eo_lexer_type_new(ls);
   FILL_BASE(def->base, ls, line, col, TYPE);
   if (ls->t.kw == KW_void)
     {
        def->type = EOLIAN_TYPE_VOID;
        def->btype = EOLIAN_TYPE_BUILTIN_VOID;
        eo_lexer_get(ls);
     }
   else if (ls->t.kw == KW___undefined_type)
     {
        def->type = EOLIAN_TYPE_UNDEFINED;
        eo_lexer_get(ls);
     }
   else
     {
        int tpid = ls->t.kw;
        def->type = EOLIAN_TYPE_REGULAR;
        check(ls, TOK_VALUE);
        if (eo_lexer_is_type_keyword(ls->t.kw))
          {
             def->btype = ls->t.kw - KW_byte + 1;
             def->base.name = eina_stringshare_ref(ls->t.value.s);
             def->base.c_name = eina_stringshare_add(eo_lexer_get_c_type(ls->t.kw));
             eo_lexer_get(ls);
             if ((tpid >= KW_accessor && tpid <= KW_list) ||
                 (tpid >= KW_slice && tpid <= KW_rw_slice) || (tpid == KW_hash))
               {
                  int bline = ls->line_number, bcol = ls->column;
                  check_next(ls, '<');
                  if (tpid == KW_future)
                    def->base_type = eo_lexer_type_release(ls, parse_type_void(ls, EINA_TRUE, EINA_TRUE));
                  else
                    def->base_type = eo_lexer_type_release(ls, parse_type(ls, EINA_TRUE, EINA_TRUE));
                  /* view-only types are not allowed to own the contents */
                  if (tpid == KW_array || tpid == KW_hash || tpid == KW_list || tpid == KW_future)
                    if ((def->base_type->move = ls->t.kw == KW_at_move))
                      eo_lexer_get(ls);
                  if (tpid == KW_hash)
                    {
                       check_next(ls, ',');
                       def->base_type->next_type =
                         eo_lexer_type_release(ls, parse_type(ls, EINA_TRUE, EINA_TRUE));
                       if ((def->base_type->next_type->move = ls->t.kw == KW_at_move))
                         eo_lexer_get(ls);
                    }
                  check_match(ls, '>', '<', bline, bcol);
               }
          }
        else
          {
             const char *bnm, *nm;
             char *fnm;
             buf = eina_strbuf_new();
             eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
             eo_lexer_context_push(ls);
             parse_name(ls, buf);
             nm = eina_strbuf_string_get(buf);
             bnm = eina_stringshare_ref(ls->filename);
             fnm = database_class_to_filename(nm);
             if (!compare_class_file(bnm, fnm))
               {
                  eina_stringshare_del(bnm);
                  if (eina_hash_find(ls->state->filenames_eo, fnm))
                    {
                       database_defer(ls->state, fnm, EINA_TRUE);
                       def->type = EOLIAN_TYPE_CLASS;
                    }
                  free(fnm);
               }
             else
               {
                  eina_stringshare_del(bnm);
                  free(fnm);
                  def->type = EOLIAN_TYPE_CLASS;
               }
             def->base.name = eina_stringshare_add(nm);
             eo_lexer_context_pop(ls);
             eo_lexer_dtor_pop(ls);
          }
     }
   return def;
}

static Eolian_Typedecl *
parse_typedef(Eo_Lexer *ls)
{
   Eolian_Typedecl *def = eo_lexer_typedecl_new(ls);
   Eina_Strbuf *buf;
   eo_lexer_get(ls);
   Eina_Stringshare *cname = NULL;
   Eina_Bool has_extern = EINA_FALSE, has_beta = EINA_FALSE, has_c_name = EINA_FALSE;
   for (;;) switch (ls->t.kw)
     {
      case KW_at_extern:
        CASE_LOCK(ls, extern, "extern qualifier");
        def->is_extern = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_beta:
        CASE_LOCK(ls, beta, "beta qualifier");
        def->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_c_name:
        CASE_LOCK(ls, c_name, "@c_name specifier");
        cname = parse_c_name(ls);
        eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_stringshare_del), (void *)cname);
        break;
      default:
        goto tags_done;
     }
tags_done:
   def->type = EOLIAN_TYPEDECL_ALIAS;
   buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   eo_lexer_context_push(ls);
   FILL_BASE(def->base, ls, ls->line_number, ls->column, TYPEDECL);
   parse_name(ls, buf);
   def->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
   if (cname)
     {
        def->base.c_name = eina_stringshare_ref(cname);
        eo_lexer_dtor_pop(ls);
     }
   else
     def->base.c_name = make_c_name(def->base.name);
   Eolian_Object *decl = _eolian_decl_get(ls, def->base.name);
   if (decl)
     {
        eo_lexer_context_restore(ls);
        redef_error(ls, decl, &def->base);
     }
   eo_lexer_context_pop(ls);
   check_next(ls, ':');
   def->base_type = eo_lexer_type_release(ls, parse_type(ls, EINA_FALSE, EINA_FALSE));
   check_next(ls, ';');
   FILL_DOC(ls, def, doc);
   eo_lexer_dtor_pop(ls);
   return def;
}

static Eolian_Constant *
parse_constant(Eo_Lexer *ls)
{
   Eolian_Constant *def = eo_lexer_constant_new(ls);
   Eina_Strbuf *buf;
   eo_lexer_get(ls);
   Eina_Stringshare *cname = NULL;
   Eina_Bool has_extern = EINA_FALSE, has_beta = EINA_FALSE, has_c_name = EINA_FALSE;
   for (;;) switch (ls->t.kw)
     {
      case KW_at_extern:
        CASE_LOCK(ls, extern, "extern qualifier");
        def->is_extern = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_beta:
        CASE_LOCK(ls, beta, "beta qualifier");
        def->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_c_name:
        CASE_LOCK(ls, c_name, "@c_name specifier");
        cname = parse_c_name(ls);
        eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_stringshare_del), (void *)cname);
        break;
      default:
        goto tags_done;
     }
tags_done:
   buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   eo_lexer_context_push(ls);
   FILL_BASE(def->base, ls, ls->line_number, ls->column, CONSTANT);
   parse_name(ls, buf);
   def->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
   if (cname)
     {
        def->base.c_name = eina_stringshare_ref(cname);
        eo_lexer_dtor_pop(ls);
     }
   else
     def->base.c_name = make_c_name(def->base.name);
   Eolian_Object *decl = _eolian_decl_get(ls, def->base.name);
   if (decl)
     {
        eo_lexer_context_restore(ls);
        redef_error(ls, decl, &def->base);
     }
   eo_lexer_context_pop(ls);
   check_next(ls, ':');
   def->base_type = eo_lexer_type_release(ls, parse_type(ls, EINA_FALSE, EINA_FALSE));
   /* constants are required to have a value */
   check(ls, '=');
   ls->expr_mode = EINA_TRUE;
   eo_lexer_get(ls);
   def->value = parse_expr(ls);
   ls->expr_mode = EINA_FALSE;
   eo_lexer_expr_release_ref(ls, def->value);
   check_next(ls, ';');
   FILL_DOC(ls, def, doc);
   eo_lexer_dtor_pop(ls);
   return def;
}

static Eolian_Error *
parse_error(Eo_Lexer *ls)
{
   Eolian_Error *def = eo_lexer_error_new(ls);
   Eina_Strbuf *buf;
   eo_lexer_get(ls);
   Eina_Stringshare *cname = NULL;
   Eina_Bool has_extern = EINA_FALSE, has_beta = EINA_FALSE, has_c_name = EINA_FALSE;
   for (;;) switch (ls->t.kw)
     {
      case KW_at_extern:
        CASE_LOCK(ls, extern, "extern qualifier");
        def->is_extern = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_beta:
        CASE_LOCK(ls, beta, "beta qualifier");
        def->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_c_name:
        CASE_LOCK(ls, c_name, "@c_name specifier");
        cname = parse_c_name(ls);
        eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_stringshare_del), (void *)cname);
        break;
      default:
        goto tags_done;
     }
tags_done:
   buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   eo_lexer_context_push(ls);
   FILL_BASE(def->base, ls, ls->line_number, ls->column, ERROR);
   parse_name(ls, buf);
   def->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
   if (cname)
     {
        def->base.c_name = eina_stringshare_ref(cname);
        eo_lexer_dtor_pop(ls);
     }
   else
     def->base.c_name = make_c_name(def->base.name);
   Eolian_Object *decl = _eolian_decl_get(ls, def->base.name);
   if (decl)
     {
        eo_lexer_context_restore(ls);
        redef_error(ls, decl, &def->base);
     }
   eo_lexer_context_pop(ls);
   check(ls, '=');
   /* we need to parse a string so switch to exprmode */
   ls->expr_mode = EINA_TRUE;
   /* consume = to get string */
   eo_lexer_get(ls);
   /* verify and switch back to plain syntax mode */
   check(ls, TOK_STRING);
   ls->expr_mode = EINA_FALSE;
   def->msg = eina_stringshare_ref(ls->t.value.s);
   eo_lexer_get(ls);
   check_next(ls, ';');
   FILL_DOC(ls, def, doc);
   eo_lexer_dtor_pop(ls);
   return def;
}

typedef struct _Eo_Ret_Def
{
   Eolian_Type *type;
   Eolian_Documentation *doc;
   Eolian_Expression *default_ret_val;
   Eina_Bool no_unused: 1;
   Eina_Bool move: 1;
   Eina_Bool by_ref: 1;
} Eo_Ret_Def;

static void
parse_return(Eo_Lexer *ls, Eo_Ret_Def *ret, Eina_Bool allow_void,
             Eina_Bool allow_def, Eina_Bool is_funcptr)
{
   eo_lexer_get(ls);
   check_next(ls, ':');
   if (allow_void)
     ret->type = parse_type_void(ls, EINA_TRUE, EINA_TRUE);
   else
     ret->type = parse_type(ls, EINA_TRUE, EINA_TRUE);
   ret->doc = NULL;
   ret->default_ret_val = NULL;
   ret->no_unused = EINA_FALSE;
   ret->move = EINA_FALSE;
   ret->by_ref = EINA_FALSE;
   if (allow_def && (ls->t.token == '('))
     {
        int line = ls->line_number, col = ls->column;
        ls->expr_mode = EINA_TRUE;
        eo_lexer_get(ls);
        ret->default_ret_val = parse_expr(ls);
        ls->expr_mode = EINA_FALSE;
        check_match(ls, ')', '(', line, col);
     }
   Eina_Bool has_no_unused = EINA_FALSE, has_move = EINA_FALSE,
             has_by_ref = EINA_FALSE;
   if (!is_funcptr) for (;;) switch (ls->t.kw)
     {
      case KW_at_no_unused:
        CASE_LOCK(ls, no_unused, "no_unused qualifier");
        ret->no_unused = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_move:
        CASE_LOCK(ls, move, "move qualifier");
        ret->move = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_by_ref:
        CASE_LOCK(ls, by_ref, "by_ref qualifier");
        ret->by_ref = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      default:
        goto end;
     }
end:
   check_next(ls, ';');
   FILL_DOC(ls, ret, doc);
}

static void
parse_param(Eo_Lexer *ls, Eina_List **params, Eina_Bool allow_inout,
            Eina_Bool is_vals, const Eolian_Function *func)
{
   Eina_Bool has_optional = EINA_FALSE,
             has_move     = EINA_FALSE,
             has_by_ref   = EINA_FALSE;
   Eolian_Function_Parameter *par = calloc(1, sizeof(Eolian_Function_Parameter));
   par->param_dir = EOLIAN_PARAMETER_IN;
   FILL_BASE(par->base, ls, ls->line_number, ls->column, FUNCTION_PARAMETER);
   *params = eina_list_append(*params, par);
   eolian_object_ref(&par->base);
   if (allow_inout && (ls->t.kw == KW_at_in))
     {
        par->param_dir = EOLIAN_PARAMETER_IN;
        eo_lexer_get(ls);
     }
   else if (allow_inout && ls->t.kw == KW_at_out)
     {
        par->param_dir = EOLIAN_PARAMETER_OUT;
        eo_lexer_get(ls);
     }
   else if (allow_inout && ls->t.kw == KW_at_inout)
     {
        par->param_dir = EOLIAN_PARAMETER_INOUT;
        eo_lexer_get(ls);
     }
   else par->param_dir = EOLIAN_PARAMETER_IN;
   check(ls, TOK_VALUE);
   par->base.name = eina_stringshare_ref(ls->t.value.s);
   eo_lexer_get(ls);
   check_next(ls, ':');
   if ((ls->klass && ls->klass->base.is_beta) || func->base.is_beta)
     {
       if (par->param_dir == EOLIAN_PARAMETER_OUT || par->param_dir == EOLIAN_PARAMETER_INOUT)
         {
            /* void is allowed for out/inout for beta-api for now to make a voidptr */
            par->type = eo_lexer_type_release(ls, parse_type_void(ls, EINA_TRUE, EINA_TRUE));
            goto type_done;
         }
     }
   par->type = eo_lexer_type_release(ls, parse_type(ls, EINA_TRUE, EINA_TRUE));
type_done:
   if ((is_vals || (par->param_dir == EOLIAN_PARAMETER_OUT)) && (ls->t.token == '('))
     {
        int line = ls->line_number, col = ls->column;
        ls->expr_mode = EINA_TRUE;
        eo_lexer_get(ls);
        par->value = parse_expr(ls);
        ls->expr_mode = EINA_FALSE;
        eo_lexer_expr_release_ref(ls, par->value);
        check_match(ls, ')', '(', line, col);
     }
   for (;;) switch (ls->t.kw)
     {
      case KW_at_optional:
        CASE_LOCK(ls, optional, "optional qualifier");
        par->optional = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_move:
        CASE_LOCK(ls, move, "move qualifier");
        par->move = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_by_ref:
        CASE_LOCK(ls, by_ref, "by_ref qualifier");
        par->by_ref = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      default:
        goto end;
     }
end:
   check_next(ls, ';');
   FILL_DOC(ls, par, doc);
}

static void
parse_params(Eo_Lexer *ls, Eina_List **params, Eina_Bool allow_inout,
             Eina_Bool is_vals, const Eolian_Function *func)
{
   int line, col;
   eo_lexer_get(ls);
   line = ls->line_number, col = ls->column;
   check_next(ls, '{');
   while (ls->t.token != '}')
     parse_param(ls, params, allow_inout, is_vals, func);
   check_match(ls, '}', '{', line, col);
}

static void
check_abstract_pure_virtual(Eo_Lexer *ls)
{
   if ((ls->klass->type != EOLIAN_CLASS_ABSTRACT) && (ls->klass->type != EOLIAN_CLASS_MIXIN))
     eo_lexer_syntax_error(ls, "@pure_virtual only allowed in abstract classes or mixins");
}

static void
parse_accessor(Eo_Lexer *ls, Eolian_Function *prop)
{
   int line, col;
   Eina_Bool has_return = EINA_FALSE, has_keys      = EINA_FALSE,
             has_values = EINA_FALSE, has_protected = EINA_FALSE,
             has_virtp  = EINA_FALSE;
   Eina_Bool is_get = (ls->t.kw == KW_get);
   if (is_get)
     {
        if (prop->base.file)
          eina_stringshare_del(prop->base.file);
        FILL_BASE(prop->base, ls, ls->line_number, ls->column, FUNCTION);
        if (prop->type == EOLIAN_PROP_SET)
          prop->type = EOLIAN_PROPERTY;
        else
          prop->type = EOLIAN_PROP_GET;
     }
   else
     {
        FILL_BASE(prop->set_base, ls, ls->line_number, ls->column, FUNCTION);
        if (prop->type == EOLIAN_PROP_GET)
          prop->type = EOLIAN_PROPERTY;
        else
          prop->type = EOLIAN_PROP_SET;
     }
   eo_lexer_get(ls);
   for (;;) switch (ls->t.kw)
     {
      case KW_at_pure_virtual:
        check_abstract_pure_virtual(ls);
        CASE_LOCK(ls, virtp, "pure_virtual qualifier");
        if (is_get) prop->impl->get_pure_virtual = EINA_TRUE;
        else prop->impl->set_pure_virtual = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_protected:
        CASE_LOCK(ls, protected, "protected qualifier");
        if (is_get) prop->get_scope = EOLIAN_SCOPE_PROTECTED;
        else prop->set_scope = EOLIAN_SCOPE_PROTECTED;
        eo_lexer_get(ls);
        break;
      default:
        goto parse_accessor;
     }
parse_accessor:
   line = ls->line_number;
   col = ls->column;
   check_next(ls, '{');
   if ((ls->t.token == TOK_DOC) && !prop->impl->common_doc)
     {
        Eolian_Object tmp;
        memset(&tmp, 0, sizeof(Eolian_Object));
        tmp.file = prop->base.file;
        tmp.line = line;
        tmp.column = col;
        tmp.unit = ls->unit;
        eolian_state_log_obj(ls->state, &tmp,
                             "%s doc without property doc for '%s.%s'",
                             is_get ? "getter" : "setter",
                             ls->klass->base.name, prop->base.name);
     }
   if (is_get)
     {
        FILL_DOC(ls, prop->impl, get_doc);
     }
   else
     {
        FILL_DOC(ls, prop->impl, set_doc);
     }
   for (;;) switch (ls->t.kw)
     {
      case KW_return:
        CASE_LOCK(ls, return, "return")
        Eo_Ret_Def ret;
        parse_return(ls, &ret, is_get, EINA_TRUE, EINA_FALSE);
        if (ret.default_ret_val)
          eo_lexer_expr_release_ref(ls, ret.default_ret_val);
        if (is_get)
          {
             prop->get_ret_type = eo_lexer_type_release(ls, ret.type);
             prop->get_return_doc = ret.doc;
             prop->get_ret_val = ret.default_ret_val;
             prop->get_return_no_unused = ret.no_unused;
             prop->get_return_by_ref = ret.by_ref;
             prop->get_return_move = ret.move;
          }
        else
          {
             prop->set_ret_type = eo_lexer_type_release(ls, ret.type);
             prop->set_return_doc = ret.doc;
             prop->set_ret_val = ret.default_ret_val;
             prop->set_return_no_unused = ret.no_unused;
             prop->set_return_by_ref = ret.by_ref;
             prop->set_return_move = ret.move;
          }
        break;
      case KW_keys:
        {
           Eina_List **stor;
           CASE_LOCK(ls, keys, "keys definition")
           stor = is_get ? &prop->prop_keys_get : &prop->prop_keys_set;
           parse_params(ls, stor, EINA_FALSE, EINA_FALSE, prop);
           break;
        }
      case KW_values:
        {
           Eina_List **stor;
           CASE_LOCK(ls, values, "values definition")
           stor = is_get ? &prop->prop_values_get : &prop->prop_values_set;
           parse_params(ls, stor, EINA_FALSE, EINA_TRUE, prop);
           break;
        }
      default:
        goto end;
     }
end:
   check_match(ls, '}', '{', line, col);
}

static void
_func_pure_virtual_set(Eo_Lexer *ls, Eolian_Function *foo_id, Eina_Bool virt)
{
   if (ls->klass->type != EOLIAN_CLASS_INTERFACE && !virt)
     return;

   if (foo_id->type == EOLIAN_PROP_GET || foo_id->type == EOLIAN_METHOD)
     foo_id->impl->get_pure_virtual = EINA_TRUE;
   else if (foo_id->type == EOLIAN_PROP_SET)
     foo_id->impl->set_pure_virtual = EINA_TRUE;
   else if (foo_id->type == EOLIAN_PROPERTY)
     foo_id->impl->get_pure_virtual = foo_id->impl->set_pure_virtual = EINA_TRUE;
}

static void
parse_property(Eo_Lexer *ls)
{
   int line, col;
   Eolian_Function *prop = NULL;
   Eolian_Implement *impl = NULL;
   Eina_Bool has_get       = EINA_FALSE, has_set    = EINA_FALSE,
             has_keys      = EINA_FALSE, has_values = EINA_FALSE,
             has_protected = EINA_FALSE, has_class  = EINA_FALSE,
             has_beta      = EINA_FALSE, has_virtp  = EINA_FALSE;
   prop = calloc(1, sizeof(Eolian_Function));
   prop->klass = ls->klass;
   prop->type = EOLIAN_UNRESOLVED;
   prop->get_scope = prop->set_scope = EOLIAN_SCOPE_PUBLIC;
   FILL_BASE(prop->base, ls, ls->line_number, ls->column, FUNCTION);
   impl = calloc(1, sizeof(Eolian_Implement));
   impl->klass = impl->implklass = ls->klass;
   impl->foo_id = prop;
   FILL_BASE(impl->base, ls, ls->line_number, ls->column, IMPLEMENT);
   prop->impl = impl;
   ls->klass->properties = eina_list_append(ls->klass->properties, prop);
   ls->klass->implements = eina_list_append(ls->klass->implements, impl);
   eolian_object_ref(&prop->base);
   eolian_object_ref(&impl->base);
   check(ls, TOK_VALUE);
   if (ls->t.kw == KW_get || ls->t.kw == KW_set)
     {
        eo_lexer_syntax_error(ls, "reserved keyword as property name");
        return;
     }
   prop->base.name = eina_stringshare_ref(ls->t.value.s);
   impl->base.name = eina_stringshare_printf("%s.%s", ls->klass->base.name, prop->base.name);
   eo_lexer_get(ls);
   for (;;) switch (ls->t.kw)
     {
      case KW_at_protected:
        CASE_LOCK(ls, protected, "protected qualifier")
        prop->get_scope = prop->set_scope = EOLIAN_SCOPE_PROTECTED;
        eo_lexer_get(ls);
        break;
      case KW_at_static:
        CASE_LOCK(ls, class, "class qualifier");
        prop->is_static = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_beta:
        CASE_LOCK(ls, beta, "beta qualifier");
        prop->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_pure_virtual:
        check_abstract_pure_virtual(ls);
        CASE_LOCK(ls, virtp, "pure_virtual qualifier");
        eo_lexer_get(ls);
        break;
      default:
        goto body;
     }
body:
   line = ls->line_number;
   col = ls->column;
   check_next(ls, '{');
   FILL_DOC(ls, prop->impl, common_doc);
   for (;;) switch (ls->t.kw)
     {
      case KW_get:
        CASE_LOCK(ls, get, "get definition")
        impl->is_prop_get = EINA_TRUE;
        parse_accessor(ls, prop);
        break;
      case KW_set:
        CASE_LOCK(ls, set, "set definition")
        impl->is_prop_set = EINA_TRUE;
        parse_accessor(ls, prop);
        break;
      case KW_keys:
        CASE_LOCK(ls, keys, "keys definition")
        parse_params(ls, &prop->prop_keys, EINA_FALSE, EINA_FALSE, prop);
        break;
      case KW_values:
        CASE_LOCK(ls, values, "values definition")
        parse_params(ls, &prop->prop_values, EINA_FALSE, EINA_TRUE, prop);
        break;
      default:
        goto end;
     }
end:
   check_match(ls, '}', '{', line, col);
   if (!has_get && !has_set)
     {
        prop->type = EOLIAN_PROPERTY;
        impl->is_prop_get = impl->is_prop_set = EINA_TRUE;
     }
   _func_pure_virtual_set(ls, prop, has_virtp);
}

static Eolian_Typedecl*
parse_function_pointer(Eo_Lexer *ls)
{
   int bline, bcol;
   int line = ls->line_number, col = ls->column;

   Eolian_Typedecl *def = eo_lexer_typedecl_new(ls);
   Eina_Strbuf *buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   Eolian_Function *meth = NULL;

   Eina_Stringshare *cname = NULL;
   Eina_Bool has_params = EINA_FALSE,
             has_return = EINA_FALSE,
             has_c_name = EINA_FALSE;

   eo_lexer_get(ls);

   def->type = EOLIAN_TYPEDECL_FUNCTION_POINTER;
   Eina_Bool has_extern = EINA_FALSE, has_beta = EINA_FALSE;
   for (;;) switch (ls->t.kw)
     {
      case KW_at_extern:
        CASE_LOCK(ls, extern, "extern qualifier");
        def->is_extern = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_beta:
        CASE_LOCK(ls, beta, "beta qualifier");
        def->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_c_name:
        CASE_LOCK(ls, c_name, "@c_name specifier");
        cname = parse_c_name(ls);
        eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_stringshare_del), (void *)cname);
        break;
      default:
        goto tags_done;
     }
tags_done:
   parse_name(ls, buf);
   def->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
   if (cname)
     {
        def->base.c_name = eina_stringshare_ref(cname);
        eo_lexer_dtor_pop(ls);
     }
   else
     def->base.c_name = make_c_name(def->base.name);
   eo_lexer_dtor_pop(ls);

   meth = calloc(1, sizeof(Eolian_Function));
   meth->klass = NULL;
   meth->type = EOLIAN_FUNCTION_POINTER;
   meth->get_scope = meth->set_scope = EOLIAN_SCOPE_PUBLIC;
   meth->base.name = eina_stringshare_add(eolian_object_short_name_get(&def->base));

   def->function_pointer = meth;
   eolian_object_ref(&meth->base);

   meth->base.is_beta = (ls->t.kw == KW_at_beta);
   if (meth->base.is_beta)
     eo_lexer_get(ls);

   bline = ls->line_number;
   bcol = ls->column;
   check_next(ls, '{');
   FILL_DOC(ls, def, doc);
   for (;;) switch (ls->t.kw)
     {
      case KW_return:
        CASE_LOCK(ls, return, "return");
        Eo_Ret_Def ret;
        parse_return(ls, &ret, EINA_FALSE, EINA_FALSE, EINA_TRUE);
        meth->get_ret_type = eo_lexer_type_release(ls, ret.type);
        meth->get_return_doc = ret.doc;
        meth->get_ret_val = NULL;
        meth->get_return_no_unused = EINA_FALSE;
        break;
      case KW_params:
        CASE_LOCK(ls, params, "params definition");
        parse_params(ls, &meth->params, EINA_TRUE, EINA_FALSE, meth);
        break;
      default:
        goto end;
     }
end:
   check_match(ls, '}', '{', bline, bcol);
   check_next(ls, ';');
   FILL_BASE(def->base, ls, line, col, TYPEDECL);
   FILL_BASE(meth->base, ls, line, col, FUNCTION);
   return def;
}

static void
parse_method(Eo_Lexer *ls)
{
   int line, col;
   Eolian_Function *meth = NULL;
   Eolian_Implement *impl = NULL;
   Eina_Bool has_const       = EINA_FALSE, has_params = EINA_FALSE,
             has_return      = EINA_FALSE, has_protected = EINA_FALSE,
             has_class       = EINA_FALSE, has_beta   = EINA_FALSE,
             has_virtp       = EINA_FALSE;
   meth = calloc(1, sizeof(Eolian_Function));
   meth->klass = ls->klass;
   meth->type = EOLIAN_METHOD;
   meth->get_scope = meth->set_scope = EOLIAN_SCOPE_PUBLIC;
   FILL_BASE(meth->base, ls, ls->line_number, ls->column, FUNCTION);
   impl = calloc(1, sizeof(Eolian_Implement));
   impl->klass = impl->implklass = ls->klass;
   impl->foo_id = meth;
   FILL_BASE(impl->base, ls, ls->line_number, ls->column, IMPLEMENT);
   meth->impl = impl;
   ls->klass->methods = eina_list_append(ls->klass->methods, meth);
   ls->klass->implements = eina_list_append(ls->klass->implements, impl);
   eolian_object_ref(&meth->base);
   eolian_object_ref(&impl->base);
   check(ls, TOK_VALUE);
   if (ls->t.kw == KW_get || ls->t.kw == KW_set)
     {
        eo_lexer_syntax_error(ls, "reserved keyword as method name");
        return;
     }
   meth->base.name = eina_stringshare_ref(ls->t.value.s);
   impl->base.name = eina_stringshare_printf("%s.%s", ls->klass->base.name, meth->base.name);
   eo_lexer_get(ls);
   for (;;) switch (ls->t.kw)
     {
      case KW_at_protected:
        CASE_LOCK(ls, protected, "protected qualifier")
        meth->get_scope = meth->set_scope = EOLIAN_SCOPE_PROTECTED;
        eo_lexer_get(ls);
        break;
      case KW_at_const:
        CASE_LOCK(ls, const, "const qualifier")
        meth->obj_is_const = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_static:
        CASE_LOCK(ls, class, "class qualifier");
        meth->is_static = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_beta:
        CASE_LOCK(ls, beta, "beta qualifier");
        meth->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_pure_virtual:
        check_abstract_pure_virtual(ls);
        CASE_LOCK(ls, virtp, "pure_virtual qualifier");
        eo_lexer_get(ls);
        break;
      default:
        goto body;
     }
body:
   line = ls->line_number;
   col = ls->column;
   check_next(ls, '{');
   FILL_DOC(ls, meth->impl, common_doc);
   for (;;) switch (ls->t.kw)
     {
      case KW_return:
        CASE_LOCK(ls, return, "return")
        Eo_Ret_Def ret;
        parse_return(ls, &ret, EINA_FALSE, EINA_TRUE, EINA_FALSE);
        if (ret.default_ret_val)
          eo_lexer_expr_release_ref(ls, ret.default_ret_val);
        meth->get_ret_type = eo_lexer_type_release(ls, ret.type);
        meth->get_return_doc = ret.doc;
        meth->get_ret_val = ret.default_ret_val;
        meth->get_return_no_unused = ret.no_unused;
        meth->get_return_by_ref = ret.by_ref;
        meth->get_return_move = ret.move;
        break;
      case KW_params:
        CASE_LOCK(ls, params, "params definition")
        parse_params(ls, &meth->params, EINA_TRUE, EINA_FALSE, meth);
        break;
      default:
        goto end;
     }
end:
   check_match(ls, '}', '{', line, col);
   _func_pure_virtual_set(ls, meth, has_virtp);
}

static void
parse_part(Eo_Lexer *ls)
{
   Eolian_Part *part = calloc(1, sizeof(Eolian_Part));
   FILL_BASE(part->base, ls, ls->line_number, ls->column, PART);
   ls->klass->parts = eina_list_append(ls->klass->parts, part);
   eolian_object_ref(&part->base);
   check(ls, TOK_VALUE);
   part->base.name = eina_stringshare_ref(ls->t.value.s);
   eo_lexer_get(ls);
   if (ls->t.kw == KW_at_beta)
     {
        part->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
     }
   check_next(ls, ':');
   Eina_Strbuf *buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   eo_lexer_context_push(ls);
   parse_name(ls, buf);
   const char *nm = eina_strbuf_string_get(buf);
   char *fnm = database_class_to_filename(nm);
   if (!eina_hash_find(ls->state->filenames_eo, fnm))
     {
        free(fnm);
        char ebuf[PATH_MAX];
        eo_lexer_context_restore(ls);
        snprintf(ebuf, sizeof(ebuf), "unknown class '%s'", nm);
        eo_lexer_syntax_error(ls, ebuf);
        return;
     }
   database_defer(ls->state, fnm, EINA_TRUE);
   free(fnm);
   part->klass_name = eina_stringshare_add(nm);
   eo_lexer_dtor_pop(ls);
   check_next(ls, ';');
   FILL_DOC(ls, part, doc);
}

static void
parse_implement(Eo_Lexer *ls, Eina_Bool iface)
{
   Eina_Strbuf *buf = NULL;
   Eolian_Implement *impl = NULL;
   int iline = ls->line_number, icol = ls->column;
   if (iface)
     check_kw(ls, KW_class);
   if (ls->t.kw == KW_class)
     {
        eo_lexer_get(ls);
        check_next(ls, '.');
        if (ls->t.kw == KW_destructor)
          {
             ls->klass->class_dtor_enable = EINA_TRUE;
             eo_lexer_get(ls);
          }
        else
          {
             check_kw_next(ls, KW_constructor);
             ls->klass->class_ctor_enable = EINA_TRUE;
          }
        check_next(ls, ';');
        return;
     }
   Eina_Bool glob_auto = EINA_FALSE, glob_empty = EINA_FALSE;
   switch (ls->t.kw)
     {
        case KW_at_auto:
          glob_auto = EINA_TRUE;
          eo_lexer_get(ls);
          break;
        case KW_at_empty:
          glob_empty = EINA_TRUE;
          eo_lexer_get(ls);
          break;
        default:
          break;
     }
   if (ls->t.token == '.')
     {
        eo_lexer_get(ls);
        if (ls->t.token != TOK_VALUE)
          eo_lexer_syntax_error(ls, "name expected");
        Eina_Stringshare *iname = eina_stringshare_printf("%s.%s",
                                                          ls->klass->base.name,
                                                          ls->t.value.s);
        Eina_List *l;
        Eolian_Implement *fimp;
        EINA_LIST_FOREACH(ls->klass->implements, l, fimp)
          if (iname == fimp->base.name)
            {
               impl = fimp;
               break;
            }
        eina_stringshare_del(iname);
        if (!impl)
          {
             eo_lexer_syntax_error(ls, "implement of non-existent function");
             return;
          }
        eo_lexer_get(ls);
        goto propbeg;
     }
   else
     {
        impl = calloc(1, sizeof(Eolian_Implement));
        FILL_BASE(impl->base, ls, iline, icol, IMPLEMENT);
        ls->klass->implements = eina_list_append(ls->klass->implements, impl);
        eolian_object_ref(&impl->base);
     }
   if (ls->t.token != TOK_VALUE)
     eo_lexer_syntax_error(ls, "class name expected");
   buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   eina_strbuf_append(buf, ls->t.value.s);
   eo_lexer_get(ls);
   check_next(ls, '.');
   if (ls->t.token != TOK_VALUE)
     eo_lexer_syntax_error(ls, "name or constructor/destructor expected");
   for (;;)
     {
        if ((ls->t.kw == KW_constructor) || (ls->t.kw == KW_destructor))
          {
             eina_strbuf_append_char(buf, '.');
             eina_strbuf_append(buf, eo_lexer_keyword_str_get(ls->t.kw));
             eo_lexer_get(ls);
             check(ls, ';');
             goto propbeg;
          }
        eina_strbuf_append_char(buf, '.');
        check(ls, TOK_VALUE);
        eina_strbuf_append(buf, ls->t.value.s);
        eo_lexer_get(ls);
        if (ls->t.token != '.') break;
        eo_lexer_get(ls);
     }
propbeg:
   if (ls->t.token == '{')
     {
        Eina_Bool has_get = EINA_FALSE, has_set = EINA_FALSE;
        eo_lexer_get(ls);
        FILL_DOC(ls, impl, common_doc);
        for (;;) switch (ls->t.kw)
          {
           case KW_get:
             CASE_LOCK(ls, get, "get specifier");
             eo_lexer_get(ls);
             impl->is_prop_get = EINA_TRUE;
             impl->get_auto = glob_auto;
             impl->get_empty = glob_empty;
             if (ls->t.kw == KW_at_auto)
               {
                  impl->get_auto = EINA_TRUE;
                  eo_lexer_get(ls);
               }
             else if (ls->t.kw == KW_at_empty)
               {
                  impl->get_empty = EINA_TRUE;
                  eo_lexer_get(ls);
               }
             check_next(ls, ';');
             FILL_DOC(ls, impl, get_doc);
             break;
           case KW_set:
             CASE_LOCK(ls, set, "set specifier");
             eo_lexer_get(ls);
             impl->is_prop_set = EINA_TRUE;
             impl->set_auto = glob_auto;
             impl->set_empty = glob_empty;
             if (ls->t.kw == KW_at_auto)
               {
                  impl->set_auto = EINA_TRUE;
                  eo_lexer_get(ls);
               }
             else if (ls->t.kw == KW_at_empty)
               {
                  impl->set_empty = EINA_TRUE;
                  eo_lexer_get(ls);
               }
             check_next(ls, ';');
             FILL_DOC(ls, impl, set_doc);
             break;
           default:
             goto propend;
          }
propend:
        if (!has_get && !has_set)
          eo_lexer_syntax_error(ls, "property implements need at least get or set specified");
        check_next(ls, '}');
     }
   else
     {
        if (glob_auto)
          impl->get_auto = impl->set_auto = EINA_TRUE;
        if (glob_empty)
          impl->get_empty = impl->set_empty = EINA_TRUE;
        check_next(ls, ';');
        FILL_DOC(ls, impl, common_doc);
     }
   if (buf)
     {
        impl->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
        eo_lexer_dtor_pop(ls);
     }
}

static void
parse_constructor(Eo_Lexer *ls)
{
   Eina_Strbuf *buf = NULL;
   Eolian_Constructor *ctor = NULL;
   ctor = calloc(1, sizeof(Eolian_Constructor));
   FILL_BASE(ctor->base, ls, ls->line_number, ls->column, CONSTRUCTOR);
   ls->klass->constructors = eina_list_append(ls->klass->constructors, ctor);
   eolian_object_ref(&ctor->base);
   if (ls->t.token == '.')
     {
        check_next(ls, '.');
        if (ls->t.token != TOK_VALUE)
          eo_lexer_syntax_error(ls, "name expected");
        ctor->base.name = eina_stringshare_printf("%s.%s",
                                                  ls->klass->base.name,
                                                  ls->t.value.s);
        eo_lexer_get(ls);
        while (ls->t.kw == KW_at_optional)
          {
             if (ls->t.kw == KW_at_optional)
               {
                  ctor->is_optional = EINA_TRUE;
               }
             eo_lexer_get(ls);
          }
        check_next(ls, ';');
        return;
     }
   check(ls, TOK_VALUE);
   buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   eina_strbuf_append(buf, ls->t.value.s);
   eo_lexer_get(ls);
   check_next(ls, '.');
   check(ls, TOK_VALUE);
   for (;;)
     {
        eina_strbuf_append_char(buf, '.');
        check(ls, TOK_VALUE);
        eina_strbuf_append(buf, ls->t.value.s);
        eo_lexer_get(ls);
        if (ls->t.token != '.') break;
        eo_lexer_get(ls);
     }
   while (ls->t.kw == KW_at_optional)
     {
        if (ls->t.kw == KW_at_optional)
          {
             ctor->is_optional = EINA_TRUE;
          }
        eo_lexer_get(ls);
     }
   check_next(ls, ';');
   ctor->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
   eo_lexer_dtor_pop(ls);
}

static void
parse_event(Eo_Lexer *ls)
{
   Eolian_Event *ev = calloc(1, sizeof(Eolian_Event));
   FILL_BASE(ev->base, ls, ls->line_number, ls->column, EVENT);
   ev->scope = EOLIAN_SCOPE_PUBLIC;
   Eina_Strbuf *buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   ls->klass->events = eina_list_append(ls->klass->events, ev);
   eolian_object_ref(&ev->base);
   check(ls, TOK_VALUE);
   eina_strbuf_append(buf, ls->t.value.s);
   eo_lexer_get(ls);
   while (ls->t.token == ',')
     {
        eo_lexer_get(ls);
        check(ls, TOK_VALUE);
        eina_strbuf_append_char(buf, ',');
        eina_strbuf_append(buf, ls->t.value.s);
        eo_lexer_get(ls);
     }
   ev->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
   eo_lexer_dtor_pop(ls);
   Eina_Bool has_scope = EINA_FALSE, has_beta = EINA_FALSE,
             has_hot   = EINA_FALSE, has_restart = EINA_FALSE;
   for (;;) switch (ls->t.kw)
     {
      case KW_at_private:
      case KW_at_protected:
        CASE_LOCK(ls, scope, "scope qualifier")
        ev->scope = (ls->t.kw == KW_at_private)
                     ? EOLIAN_SCOPE_PRIVATE
                     : EOLIAN_SCOPE_PROTECTED;
        eo_lexer_get(ls);
        break;
      case KW_at_beta:
        CASE_LOCK(ls, beta, "beta qualifier")
        ev->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_hot:
        CASE_LOCK(ls, hot, "hot qualifier");
        ev->is_hot = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_restart:
        CASE_LOCK(ls, restart, "restart qualifier");
        ev->is_restart = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      default:
        goto end;
     }
end:
   check_next(ls, ':');
   ev->type = eo_lexer_type_release(ls, parse_type_void(ls, EINA_TRUE, EINA_TRUE));
   check(ls, ';');
   eo_lexer_get(ls);
   FILL_DOC(ls, ev, doc);
   ev->klass = ls->klass;
}

static void
parse_methods(Eo_Lexer *ls)
{
   int line, col;
   eo_lexer_get(ls);
   line = ls->line_number, col = ls->column;
   check_next(ls, '{');
   while (ls->t.token != '}')
     {
        if (ls->t.kw == KW_at_property)
          {
             eo_lexer_get(ls);
             parse_property(ls);
             continue;
          }
        parse_method(ls);
     }
   check_match(ls, '}', '{', line, col);
}

static void
parse_parts(Eo_Lexer *ls)
{
   int line, col;
   eo_lexer_get(ls);
   line = ls->line_number, col = ls->column;
   check_next(ls, '{');
   while (ls->t.token != '}')
     parse_part(ls);
   check_match(ls, '}', '{', line, col);
}

static void
parse_implements(Eo_Lexer *ls, Eina_Bool iface)
{
   int line, col;
   eo_lexer_get(ls);
   line = ls->line_number, col = ls->column;
   check_next(ls, '{');
   while (ls->t.token != '}')
     parse_implement(ls, iface);
   check_match(ls, '}', '{', line, col);
}

static void
parse_constructors(Eo_Lexer *ls)
{
   int line, col;
   eo_lexer_get(ls);
   line = ls->line_number, col = ls->column;
   check_next(ls, '{');
   while (ls->t.token != '}')
     parse_constructor(ls);
   check_match(ls, '}', '{', line, col);
}

static void
parse_events(Eo_Lexer *ls)
{
   int line, col;
   eo_lexer_get(ls);
   line = ls->line_number;
   col = ls->column;
   check(ls, '{');
   eo_lexer_get(ls);
   while (ls->t.token != '}')
     parse_event(ls);
   check_match(ls, '}', '{', line, col);
}

static void
_validate_pfx(Eo_Lexer *ls)
{
   char ebuf[PATH_MAX];
   check(ls, TOK_VALUE);
   const char *str = ls->t.value.s;
   if ((*str != '_') && ((*str < 'a') || (*str > 'z')))
     goto error;
   for (++str; *str; ++str)
     {
        if (*str == '_')
          continue;
        if ((*str >= 'a') && (*str <= 'z'))
          continue;
        if ((*str >= '0') && (*str <= '9'))
          continue;
        goto error;
     }
   return;
error:
   snprintf(ebuf, sizeof(ebuf), "invalid prefix '%s'", ls->t.value.s);
   eo_lexer_syntax_error(ls, ebuf);
}

static void
parse_class_body(Eo_Lexer *ls, Eolian_Class_Type type)
{
   Eina_Bool has_c_prefix     = EINA_FALSE,
             has_event_c_prefix = EINA_FALSE,
             has_data          = EINA_FALSE,
             has_methods       = EINA_FALSE,
             has_parts         = EINA_FALSE,
             has_implements    = EINA_FALSE,
             has_constructors  = EINA_FALSE,
             has_events        = EINA_FALSE;
   FILL_DOC(ls, ls->klass, doc);
   if (type == EOLIAN_CLASS_INTERFACE)
     {
        ls->klass->data_type = eina_stringshare_add("null");
     }
   for (;;) switch (ls->t.kw)
     {
      case KW_c_prefix:
        CASE_LOCK(ls, c_prefix, "c prefix definition")
        eo_lexer_get(ls);
        check_next(ls, ':');
        _validate_pfx(ls);
        ls->klass->c_prefix = eina_stringshare_ref(ls->t.value.s);
        eo_lexer_get(ls);
        check_next(ls, ';');
        break;
      case KW_event_c_prefix:
        CASE_LOCK(ls, event_c_prefix, "event prefix definition")
        eo_lexer_get(ls);
        check_next(ls, ':');
        _validate_pfx(ls);
        ls->klass->ev_prefix = eina_stringshare_ref(ls->t.value.s);
        eo_lexer_get(ls);
        check_next(ls, ';');
        break;
      case KW_data:
        if (type == EOLIAN_CLASS_INTERFACE) return;
        CASE_LOCK(ls, data, "data definition")
        eo_lexer_get(ls);
        check_next(ls, ':');
        check(ls, TOK_VALUE);
        ls->klass->data_type = eina_stringshare_ref(ls->t.value.s);
        eo_lexer_get(ls);
        check_next(ls, ';');
        break;
      case KW_methods:
        CASE_LOCK(ls, methods, "methods definition")
        parse_methods(ls);
        break;
      case KW_parts:
        CASE_LOCK(ls, parts, "parts definition")
        parse_parts(ls);
        break;
      case KW_implements:
        CASE_LOCK(ls, implements, "implements definition")
        parse_implements(ls, type == EOLIAN_CLASS_INTERFACE);
        break;
      case KW_constructors:
        if (type == EOLIAN_CLASS_INTERFACE || type == EOLIAN_CLASS_MIXIN)
          return;
        CASE_LOCK(ls, constructors, "constructors definition")
        parse_constructors(ls);
        break;
      case KW_events:
        CASE_LOCK(ls, events, "events definition")
        parse_events(ls);
        break;
      default:
        return;
     }
}

static void
_inherit_dep(Eo_Lexer *ls, Eina_Strbuf *buf, Eina_Bool parent)
{
   char ebuf[PATH_MAX];
   const char *iname;
   char *fnm;
   eina_strbuf_reset(buf);
   eo_lexer_context_push(ls);
   parse_name(ls, buf);
   iname = eina_strbuf_string_get(buf);
   fnm = database_class_to_filename(iname);
   if (compare_class_file(fnm, ls->filename))
     {
        free(fnm);
        eo_lexer_context_restore(ls);
        snprintf(ebuf, sizeof(ebuf), "class '%s' cannot inherit from itself",
                 iname);
        eo_lexer_syntax_error(ls, ebuf);
        return; /* unreachable (longjmp above), make static analysis shut up */
     }
   if (!eina_hash_find(ls->state->filenames_eo, fnm))
     {
        free(fnm);
        eo_lexer_context_restore(ls);
        snprintf(ebuf, sizeof(ebuf), "unknown inherit '%s'", iname);
        eo_lexer_syntax_error(ls, ebuf);
        return;
     }

   Eina_Stringshare *inames = eina_stringshare_add(iname), *oiname = NULL;
   /* never allow duplicate inherits */
   if (!parent)
     {
        Eina_List *l;
        if (inames == ls->klass->parent_name)
          goto inherit_dup;
        EINA_LIST_FOREACH(ls->klass->extends, l, oiname)
          {
             if (inames == oiname)
               goto inherit_dup;
          }
     }
   database_defer(ls->state, fnm, EINA_TRUE);
   if (parent)
     ls->klass->parent_name = inames;
   else
     ls->klass->extends = eina_list_append(ls->klass->extends, inames);
   free(fnm);
   eo_lexer_context_pop(ls);
   return;

inherit_dup:
   free(fnm);
   eina_stringshare_del(inames);
   eo_lexer_context_restore(ls);
   snprintf(ebuf, sizeof(ebuf), "duplicate inherit '%s'", iname);
   eo_lexer_syntax_error(ls, ebuf);
}

static void
_requires_add(Eo_Lexer *ls, Eina_Strbuf *buf)
{
   const char *required;
   char *fnm;
   Eina_List *l;
   const char *oname;
   char ebuf[PATH_MAX];

   eina_strbuf_reset(buf);
   eo_lexer_context_push(ls);
   parse_name(ls, buf);
   required = eina_stringshare_add(eina_strbuf_string_get(buf));

   EINA_LIST_FOREACH(ls->klass->requires, l, oname)
     if (required == oname)
       {
          eo_lexer_context_restore(ls);
          eina_stringshare_del(required);
          snprintf(ebuf, sizeof(ebuf), "duplicate entry '%s'", oname);
          eo_lexer_syntax_error(ls, ebuf);
          return;
       }

   fnm = database_class_to_filename(required);

   ls->klass->requires = eina_list_append(ls->klass->requires, required);
   database_defer(ls->state, fnm, EINA_TRUE);
   eo_lexer_context_pop(ls);

   free(fnm);
}

static void
_composite_add(Eo_Lexer *ls, Eina_Strbuf *buf)
{
   const char *oname;
   char ebuf[PATH_MAX];
   Eina_List *l;

   eina_strbuf_reset(buf);
   eo_lexer_context_push(ls);
   parse_name(ls, buf);
   const char *nm = eina_stringshare_add(eina_strbuf_string_get(buf));

   EINA_LIST_FOREACH(ls->klass->composite, l, oname)
     if (nm == oname)
       {
          eo_lexer_context_restore(ls);
          snprintf(ebuf, sizeof(ebuf), "duplicate entry '%s'", nm);
          eina_stringshare_del(nm);
          eo_lexer_syntax_error(ls, ebuf);
          return;
       }

   char *fnm = database_class_to_filename(nm);
   if (!eina_hash_find(ls->state->filenames_eo, fnm))
     {
        free(fnm);
        eo_lexer_context_restore(ls);
        snprintf(ebuf, sizeof(ebuf), "unknown interface '%s'", nm);
        eina_stringshare_del(nm);
        eo_lexer_syntax_error(ls, ebuf);
        return;
     }
   /* composite == definitely a dependency */
   database_defer(ls->state, fnm, EINA_TRUE);
   free(fnm);
   ls->klass->composite = eina_list_append(ls->klass->composite, nm);
   eo_lexer_context_pop(ls);
}

static void
parse_class(Eo_Lexer *ls, Eolian_Class_Type type)
{
   const char *bnm;
   char *fnm;
   Eina_Bool same;
   int line, col;
   Eina_Strbuf *buf = eina_strbuf_new();
   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
   ls->klass = (Eolian_Class *)eo_lexer_node_new(ls, sizeof(Eolian_Class));
   FILL_BASE(ls->klass->base, ls, ls->line_number, ls->column, CLASS);
   eo_lexer_get(ls);
   ls->klass->type = type;
   eo_lexer_context_push(ls);
   Eina_Stringshare *cname = NULL;
   Eina_Bool has_beta = EINA_FALSE, has_c_name = EINA_FALSE;
   for (;;) switch (ls->t.kw)
     {
      case KW_at_beta:
        CASE_LOCK(ls, beta, "beta qualifier");
        ls->klass->base.is_beta = EINA_TRUE;
        eo_lexer_get(ls);
        break;
      case KW_at_c_name:
        CASE_LOCK(ls, c_name, "@c_name specifier");
        cname = parse_c_name(ls);
        eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_stringshare_del), (void *)cname);
        break;
      default:
        goto tags_done;
     }
tags_done:
   parse_name(ls, buf);
   bnm = eina_stringshare_ref(ls->filename);
   fnm = database_class_to_filename(eina_strbuf_string_get(buf));
   same = compare_class_file(bnm, fnm);
   eina_stringshare_del(bnm);
   free(fnm);
   if (!same)
     {
        eo_lexer_context_restore(ls);
        eo_lexer_syntax_error(ls, "class and file names differ");
     }
   ls->klass->base.name = eina_stringshare_add(eina_strbuf_string_get(buf));
   if (cname)
     {
        ls->klass->base.c_name = eina_stringshare_ref(cname);
        eo_lexer_dtor_pop(ls);
     }
   else
     ls->klass->base.c_name = make_c_name(ls->klass->base.name);
   Eolian_Object *decl = _eolian_decl_get(ls, ls->klass->base.name);
   if (decl)
     {
        eo_lexer_context_restore(ls);
        redef_error(ls, decl, &ls->klass->base);
     }
   eo_lexer_context_pop(ls);
   eo_lexer_dtor_pop(ls);

   Eina_Bool is_reg = (type == EOLIAN_CLASS_REGULAR) || (type == EOLIAN_CLASS_ABSTRACT);
   if (ls->t.token != '{')
     {
        line = ls->line_number;
        col = ls->column;
        Eina_Strbuf *ibuf = eina_strbuf_new();
        eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), ibuf);
        /* new inherits syntax, keep alongside old for now */
        if (ls->t.kw == KW_requires)
          {
             if (type != EOLIAN_CLASS_MIXIN)
               {
                  eo_lexer_syntax_error(ls, "\"requires\" keyword is only needed for mixin classes");
               }
             eo_lexer_get(ls);
             do
               _requires_add(ls, ibuf);
             while (test_next(ls, ','));
          }

        if (ls->t.kw == KW_extends || (is_reg && (ls->t.kw == KW_implements)))
          {
             Eina_Bool ext = (ls->t.kw == KW_extends);
             eo_lexer_get(ls);
             if (is_reg && ext)
               {
                  /* regular class can have a parent, but just one */
                  _inherit_dep(ls, ibuf, EINA_TRUE);
                  /* followed by composites */
                  if (ls->t.kw == KW_composites)
                    goto noimp_comp;
                  /* if not followed by implements, we're done */
                  if (ls->t.kw != KW_implements)
                    {
                       eo_lexer_dtor_pop(ls);
                       goto inherit_done;
                    }
                  eo_lexer_get(ls);
               }
             do
               _inherit_dep(ls, ibuf, EINA_FALSE);
             while (test_next(ls, ','));
          }

noimp_comp:
        if (ls->t.kw == KW_composites)
          {
             if (type == EOLIAN_CLASS_INTERFACE)
                eo_lexer_syntax_error(ls, "interfaces cannot composite");
             eo_lexer_get(ls);
             do
               _composite_add(ls, ibuf);
             while (test_next(ls, ','));
          }

        eo_lexer_dtor_pop(ls);
     }
inherit_done:
   line = ls->line_number;
   col = ls->column;
   check_next(ls, '{');
   parse_class_body(ls, type);
   check_match(ls, '}', '{', line, col);
}

static Eina_Bool
parse_unit(Eo_Lexer *ls, Eina_Bool eot)
{
   switch (ls->t.kw)
     {
      case KW_abstract:
        if (eot) goto def;
        parse_class(ls, EOLIAN_CLASS_ABSTRACT);
        goto found_class;
      case KW_class:
        if (eot) goto def;
        parse_class(ls, EOLIAN_CLASS_REGULAR);
        goto found_class;
      case KW_mixin:
        if (eot) goto def;
        parse_class(ls, EOLIAN_CLASS_MIXIN);
        goto found_class;
      case KW_interface:
        if (eot) goto def;
        parse_class(ls, EOLIAN_CLASS_INTERFACE);
        goto found_class;
      case KW_import:
      case KW_parse:
        {
           Eina_Bool isdep = (ls->t.kw == KW_import);
           Eina_Strbuf *buf = eina_strbuf_new();
           eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
           char errbuf[PATH_MAX];
           eo_lexer_get(ls);
           check(ls, TOK_VALUE);
           eina_strbuf_append(buf, ls->t.value.s);
           eina_strbuf_append(buf, ".eot");
           if (!eina_hash_find(ls->state->filenames_eot, eina_strbuf_string_get(buf)))
             {
                size_t buflen = eina_strbuf_length_get(buf);
                eina_strbuf_remove(buf, buflen - 1, buflen);
                if (!eina_hash_find(ls->state->filenames_eo, eina_strbuf_string_get(buf)))
                  {
                     eo_lexer_dtor_pop(ls);
                     snprintf(errbuf, sizeof(errbuf),
                              "unknown import '%s'", ls->t.value.s);
                     eo_lexer_syntax_error(ls, errbuf);
                  }
             }
           database_defer(ls->state, eina_strbuf_string_get(buf), isdep);
           eo_lexer_dtor_pop(ls);
           eo_lexer_get(ls);
           check_next(ls, ';');
           break;
        }
      case KW_type:
        {
           database_type_add(ls->unit,
             eo_lexer_typedecl_release(ls, parse_typedef(ls)));
           break;
        }
      case KW_function:
        {
           database_type_add(ls->unit,
             eo_lexer_typedecl_release(ls, parse_function_pointer(ls)));
           break;
        }
      case KW_const:
        {
           database_constant_add(ls->unit, eo_lexer_constant_release(ls, parse_constant(ls)));
           break;
        }
      case KW_error:
        database_error_add(ls->unit, eo_lexer_error_release(ls, parse_error(ls)));
        break;
      case KW_struct:
      case KW_enum:
        {
           Eina_Bool is_enum = (ls->t.kw == KW_enum);
           const char *name;
           int line, col;
           const char *freefunc = NULL, *cname = NULL;
           Eina_Strbuf *buf;
           eo_lexer_get(ls);
           Eina_Bool has_extern = EINA_FALSE, has_free   = EINA_FALSE,
                     has_beta   = EINA_FALSE, has_c_name = EINA_FALSE;
           for (;;) switch (ls->t.kw)
             {
              case KW_at_extern:
                CASE_LOCK(ls, extern, "@extern qualifier")
                eo_lexer_get(ls);
                break;
              case KW_at_beta:
                CASE_LOCK(ls, beta, "@beta qualifier")
                eo_lexer_get(ls);
                break;
              case KW_at_c_name:
                CASE_LOCK(ls, c_name, "@c_name specifier");
                cname = parse_c_name(ls);
                eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_stringshare_del), (void *)cname);
                break;
              case KW_at_free:
                {
                   CASE_LOCK(ls, free, "@free qualifier")
                   if (is_enum)
                     eo_lexer_syntax_error(ls, "enums cannot have @free");
                   eo_lexer_get(ls);
                   int pline = ls->line_number, pcol = ls->column;
                   check_next(ls, '(');
                   check(ls, TOK_VALUE);
                   freefunc = eina_stringshare_add(ls->t.value.s);
                   eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_stringshare_del), (void *)freefunc);
                   eo_lexer_get(ls);
                   check_match(ls, ')', '(', pline, pcol);
                   break;
                }
              default:
                goto postparams;
             }
postparams:
           buf = eina_strbuf_new();
           eo_lexer_dtor_push(ls, EINA_FREE_CB(eina_strbuf_free), buf);
           eo_lexer_context_push(ls);
           line = ls->line_number;
           col = ls->column;
           parse_name(ls, buf);
           name = eina_stringshare_add(eina_strbuf_string_get(buf));
           Eolian_Object *decl = _eolian_decl_get(ls, name);
           if (decl)
             {
                eina_stringshare_del(name);
                eo_lexer_context_restore(ls);
                Eolian_Typedecl tdecl;
                tdecl.base.type = EOLIAN_OBJECT_TYPEDECL;
                tdecl.type =
                  (is_enum ? EOLIAN_TYPEDECL_ENUM : EOLIAN_TYPEDECL_STRUCT);
                redef_error(ls, decl, &tdecl.base);
             }
           eo_lexer_context_pop(ls);
           eo_lexer_dtor_pop(ls);
           if (!is_enum && ls->t.token == ';')
             {
                Eolian_Typedecl *def = eo_lexer_typedecl_new(ls);
                def->is_extern = has_extern;
                def->base.is_beta = has_beta;
                def->type = EOLIAN_TYPEDECL_STRUCT_OPAQUE;
                if (freefunc)
                  {
                     def->freefunc = eina_stringshare_ref(freefunc);
                     def->ownable = EINA_TRUE;
                  }
                def->base.name = name;
                if (cname)
                  def->base.c_name = eina_stringshare_ref(cname);
                /* we can't know the order, pop when both are filled */
                if (freefunc && cname)
                  {
                     eo_lexer_dtor_pop(ls);
                     eo_lexer_dtor_pop(ls);
                  }
                else if (freefunc || cname)
                  eo_lexer_dtor_pop(ls);
                if (!def->base.c_name)
                  def->base.c_name = make_c_name(name);
                eo_lexer_get(ls);
                FILL_DOC(ls, def, doc);
                FILL_BASE(def->base, ls, line, col, TYPEDECL);
                database_struct_add(ls->unit, eo_lexer_typedecl_release(ls, def));
                break;
             }
           if (is_enum)
             parse_enum(ls, name, has_extern, has_beta, line, col, cname);
           else
             parse_struct(ls, name, has_extern, has_beta, line, col, freefunc, cname);
           break;
        }
      def:
      default:
        eo_lexer_syntax_error(ls, "invalid token");
        break;
     }
   return EINA_FALSE;
found_class:
   database_object_add(ls->unit, &ls->klass->base);
   return EINA_TRUE;
}

static void
parse_chunk(Eo_Lexer *ls, Eina_Bool eot)
{
   Eina_Bool parsing_header = EINA_TRUE;
   Eina_Bool has_version = EINA_FALSE;
   while (ls->t.token >= 0)
     switch (ls->t.kw)
       {
        case KW_hash_version:
          {
             CASE_LOCK(ls, version, "#version specifier");
             if (!parsing_header)
               eo_lexer_syntax_error(ls, "header keyword outside of unit header");
             eo_lexer_get(ls);

             check(ls, TOK_NUMBER);
             if (ls->t.kw != NUM_INT)
               eo_lexer_syntax_error(ls, "invalid #version value");
             if (ls->t.value.u > USHRT_MAX)
               eo_lexer_syntax_error(ls, "#version too high");
             else if (ls->t.value.u < 1)
               eo_lexer_syntax_error(ls, "#version too low");

             ls->unit->version = (unsigned short)(ls->t.value.u);
             if (ls->unit->version > EOLIAN_FILE_FORMAT_VERSION)
               eo_lexer_syntax_error(ls, "file version too new for this version of Eolian");
             eo_lexer_get(ls);
             break;
           }
        default:
          parsing_header = EINA_FALSE;
          /* set eot to EINA_TRUE so that we only allow parsing of one class */
          if (parse_unit(ls, eot))
            eot = EINA_TRUE;
          break;
       }
}

Eolian_Unit *
eo_parser_database_fill(Eolian_Unit *parent, const char *filename, Eina_Bool eot)
{
   int status = 0;
   const char *fsl = strrchr(filename, '/');
   const char *bsl = strrchr(filename, '\\');
   const char *fname = NULL;
   if (fsl || bsl)
     fname = eina_stringshare_add((fsl > bsl) ? (fsl + 1) : (bsl + 1));
   else
     fname = eina_stringshare_add(filename);

   Eolian_Unit *ret = eina_hash_find(parent->state->main.units, fname);
   if (!ret)
     ret = eina_hash_find(parent->state->staging.units, fname);

   if (ret)
     {
        if ((parent != ret) && !eina_hash_find(parent->children, fname))
          eina_hash_add(parent->children, fname, ret);
        eina_stringshare_del(fname);
        return ret;
     }

   Eo_Lexer *ls = eo_lexer_new(parent->state, filename);
   if (!ls)
     {
        eolian_state_log(parent->state, "unable to create lexer for file '%s'",
                         filename);
        goto error;
     }

   /* read first token */
   eo_lexer_get(ls);

   if ((status = setjmp(ls->err_jmp)))
     goto error;

   parse_chunk(ls, eot);
   if (eot) goto done;

   Eolian_Class *cl;
   if (!(cl = ls->klass))
     {
        eolian_state_log(ls->state, "no class for file '%s'", filename);
        goto error;
     }
   ls->klass = NULL;
   EOLIAN_OBJECT_ADD(ls->unit, cl->base.name, cl, classes);
   eina_hash_set(ls->state->staging.classes_f, cl->base.file, cl);
   eo_lexer_node_release(ls, &cl->base);

done:
   ret = ls->unit;
   eina_hash_add(parent->children, fname, ret);
   eina_stringshare_del(fname);

   eo_lexer_free(ls);
   return ret;

error:
   eina_stringshare_del(fname);
   eo_lexer_free(ls);
   switch (status)
     {
      case EO_LEXER_ERROR_OOM:
        eolian_state_panic(parent->state, "out of memory");
        break;
      default:
        break;
     }
   return NULL;
}
