eolian: introduce the keyword required
authorMarcel Hollerbach <mail@marcel-hollerbach.de>
Thu, 10 Jan 2019 11:10:47 +0000 (12:10 +0100)
committerJiyoun Park <jy0703.park@samsung.com>
Wed, 16 Jan 2019 02:26:53 +0000 (11:26 +0900)
This introduces a new keyword called required. It only works on mixins.
You can specify a list of regular/abstract classes in there.
Classes specified after the required keyword are later used to verify
the usage of the mixin. With this feature a mixin can define a list of
types that the inheriting object (the object that inherits from a mixin)
needs to fullfill, if one class that is required is not in the
implemented classes, then eolian will bail out.

Differential Revision: https://phab.enlightenment.org/D7584

src/Makefile_Eolian.am
src/lib/eolian/database_validate.c
src/lib/eolian/eo_lexer.h
src/lib/eolian/eo_parser.c
src/lib/eolian/eolian_database.h
src/tests/eolian/data/class_requires.eo [new file with mode: 0644]
src/tests/eolian/data/mixins_require.eo [new file with mode: 0644]
src/tests/eolian/eolian_parsing.c

index 6d607ef..5e1e2d0 100644 (file)
@@ -103,6 +103,8 @@ tests/eolian/data/typedef.eo \
 tests/eolian/data/var.eo \
 tests/eolian/data/function_types.eot \
 tests/eolian/data/import_types.eot \
+tests/eolian/data/class_requires.eo \
+tests/eolian/data/mixins_require.eo \
 tests/eolian/data_aux/aux_a.eo \
 tests/eolian/data_aux/aux_b.eo \
 tests/eolian/data_aux/aux_c.eo
index a96e389..09004c8 100644 (file)
@@ -508,6 +508,12 @@ _get_impl_class(const Eolian_Class *cl, const char *cln)
         if (fcl)
           return fcl;
      }
+   EINA_LIST_FOREACH(cl->requires, l, icl)
+     {
+        const Eolian_Class *fcl = _get_impl_class(icl, cln);
+        if (fcl)
+          return fcl;
+     }
    EINA_LIST_FOREACH(cl->extends, l, icl)
      {
         const Eolian_Class *fcl = _get_impl_class(icl, cln);
@@ -743,9 +749,10 @@ _db_fill_inherits(Eolian_Class *cl, Eina_Hash *fhash)
    if (eina_hash_find(cl->base.unit->state->main.unit.classes, cl->base.name))
      return EINA_TRUE;
 
-   Eina_List *il = cl->extends;
+   Eina_List *il = cl->extends, *rl = cl->requires;
    Eina_Stringshare *inn = NULL;
    cl->extends = NULL;
+   cl->requires = NULL;
    Eina_Bool succ = EINA_TRUE;
 
    if (cl->parent_name)
@@ -771,6 +778,30 @@ _db_fill_inherits(Eolian_Class *cl, Eina_Hash *fhash)
         succ = _db_fill_inherits(out_cl, fhash);
      }
 
+   if (succ && cl->type == EOLIAN_CLASS_MIXIN)
+     {
+        EINA_LIST_FREE(rl, inn)
+          {
+             Eolian_Class *out_cl = NULL;
+             succ = _db_swap_inherit(cl, succ, inn, &out_cl);
+             if (succ && !(out_cl->type == EOLIAN_CLASS_REGULAR || out_cl->type == EOLIAN_CLASS_ABSTRACT))
+               {
+                  char buf[PATH_MAX];
+                  snprintf(buf, sizeof(buf), "requires only allows regulars or abstracts");
+                  _obj_error(&cl->base, buf);
+                  succ = EINA_FALSE;
+               }
+             if (succ)
+               {
+                 _db_fill_inherits(out_cl, fhash);
+               }
+             if (!succ)
+               continue;
+             if (!eina_list_data_find(cl->requires, out_cl))
+               cl->requires = eina_list_append(cl->requires, out_cl);
+          }
+     }
+
    /* failed on the way, no point in filling further
     * the failed stuff will get dropped so it's ok if it's inconsistent
     */
@@ -805,6 +836,24 @@ _validate_implement(Eolian_Implement *impl)
    return _validate(&impl->base);
 }
 
+static Eina_List*
+_required_classes(Eolian_Class *mixin)
+{
+   Eina_List *result = NULL, *n;
+   Eolian_Class *extension;
+
+
+   result = eina_list_clone(mixin->requires);
+
+   if (mixin->parent)
+     result = eina_list_merge(result, _required_classes(mixin->parent));
+
+   EINA_LIST_FOREACH(mixin->extends, n, extension)
+     result = eina_list_merge(result, _required_classes(extension));
+
+   return result;
+}
+
 static Eina_Bool
 _validate_class(Validate_State *vals, Eolian_Class *cl,
                 Eina_Hash *nhash, Eina_Hash *ehash, Eina_Hash *chash)
@@ -815,6 +864,7 @@ _validate_class(Validate_State *vals, Eolian_Class *cl,
    Eolian_Part *part;
    Eolian_Implement *impl;
    Eolian_Class *icl;
+   Eina_List *required_classes = NULL;
 
    if (!cl)
      return EINA_FALSE; /* if this happens something is very wrong though */
@@ -849,6 +899,17 @@ _validate_class(Validate_State *vals, Eolian_Class *cl,
 
    EINA_LIST_FOREACH(cl->extends, l, icl)
      {
+        if (icl->type == EOLIAN_CLASS_MIXIN)
+          {
+             Eina_List *res = _required_classes(icl);
+             Eolian_Class *required_class;
+             Eina_List *n;
+             EINA_LIST_FOREACH(res, n, required_class)
+               {
+                 if (!eina_list_data_find(required_classes, required_class))
+                   required_classes = eina_list_append(required_classes, required_class);
+               }
+          }
         if (!valid && vals->ext_regular) switch (icl->type)
           {
            case EOLIAN_CLASS_REGULAR:
@@ -869,6 +930,35 @@ _validate_class(Validate_State *vals, Eolian_Class *cl,
         if (!_validate_class(vals, icl, nhash, ehash, chash))
           return EINA_FALSE;
      }
+   if (cl->type == EOLIAN_CLASS_ABSTRACT || cl->type == EOLIAN_CLASS_REGULAR)
+     {
+        //walk up the parent list and remove all classes from there
+        icl = cl;
+        while (icl)
+          {
+             required_classes = eina_list_remove(required_classes, icl);
+             icl = icl->parent;
+          }
+        //if there are a few left, drop, and error
+        if (required_classes)
+          {
+             Eina_Strbuf *classes = eina_strbuf_new();
+             Eolian_Class *required_class;
+             Eina_List *n;
+             EINA_LIST_FOREACH(required_classes, n, required_class)
+               {
+                   eina_strbuf_append(classes, required_class->base.name);
+                   eina_strbuf_append_char(classes, ' ');
+               }
+             char buf[PATH_MAX];
+             snprintf(buf, sizeof(buf), "required classes %sare not in the inherit chain of %s",
+                      eina_strbuf_string_get(classes), cl->base.name);
+             eina_strbuf_free(classes);
+             _obj_error(&cl->base, buf);
+             return EINA_FALSE;
+          }
+     }
+
 
    EINA_LIST_FOREACH(cl->properties, l, func)
      if (!_validate_function(vals, func, nhash))
index d805357..522cc41 100644 (file)
@@ -28,7 +28,7 @@ enum Tokens
     KW(destructor), KW(eo), KW(eo_prefix), KW(event_prefix), KW(events), \
     KW(extends), KW(free), KW(get), KW(implements), KW(import), KW(interface), \
     KW(keys), KW(legacy), KW(legacy_prefix), KW(methods), KW(mixin), KW(params), \
-    KW(parse), KW(parts), KW(ptr), KW(set), KW(type), KW(values), KW(var), \
+    KW(parse), KW(parts), KW(ptr), KW(set), KW(type), KW(values), KW(var), KW(requires), \
     \
     KWAT(auto), KWAT(beta), KWAT(class), KWAT(const), KWAT(cref), KWAT(empty), \
     KWAT(extern), KWAT(free), KWAT(hot), KWAT(in), KWAT(inout), KWAT(nonull), \
index 37bed0a..a06b69b 100644 (file)
@@ -2010,6 +2010,24 @@ inherit_dup:
 }
 
 static void
+_requires_add(Eo_Lexer *ls, Eina_Strbuf *buf)
+{
+   const char *required;
+   char *fnm;
+
+   eo_lexer_context_push(ls);
+   parse_name(ls, buf);
+   required = eina_strbuf_string_get(buf);
+   fnm = database_class_to_filename(required);
+
+   ls->klass->requires = eina_list_append(ls->klass->requires, eina_stringshare_add(required));
+   database_defer(ls->state, fnm, EINA_TRUE);
+   eo_lexer_context_pop(ls);
+
+   free(fnm);
+}
+
+static void
 parse_class(Eo_Lexer *ls, Eolian_Class_Type type)
 {
    const char *bnm;
@@ -2052,6 +2070,20 @@ parse_class(Eo_Lexer *ls, Eolian_Class_Type type)
         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.token == '{')
+               goto inherit_done;
+          }
+
         if (ls->t.kw == KW_extends || (is_reg && (ls->t.kw == KW_implements)))
           {
              Eina_Bool ext = (ls->t.kw == KW_extends);
index 9804449..fdaee09 100644 (file)
@@ -191,6 +191,7 @@ struct _Eolian_Class
    Eina_List *constructors; /* Eolian_Constructor */
    Eina_List *events; /* Eolian_Event */
    Eina_List *parts; /* Eolian_Part */
+   Eina_List *requires; /* a list of required other classes only used internally */
    Eina_Bool class_ctor_enable:1;
    Eina_Bool class_dtor_enable:1;
 };
diff --git a/src/tests/eolian/data/class_requires.eo b/src/tests/eolian/data/class_requires.eo
new file mode 100644 (file)
index 0000000..173c660
--- /dev/null
@@ -0,0 +1,8 @@
+import base;
+import mixins_require;
+
+class Class.Requires (Base, Mixins.Require) {
+  methods {
+
+  }
+}
diff --git a/src/tests/eolian/data/mixins_require.eo b/src/tests/eolian/data/mixins_require.eo
new file mode 100644 (file)
index 0000000..474e1a3
--- /dev/null
@@ -0,0 +1,13 @@
+import base;
+import class_simple;
+
+mixin Mixins.Require requires Base {
+  methods {
+    test {
+
+    }
+  }
+  implements {
+    Base.constructor;
+  }
+}
index 6df473f..d93906b 100644 (file)
@@ -1552,6 +1552,66 @@ EFL_START_TEST(eolian_parts)
 }
 EFL_END_TEST
 
+EFL_START_TEST(eolian_mixins_require)
+{
+   const Eolian_Unit *unit;
+   const Eolian_Class *cl;
+   const Eolian_Class *base;
+
+   Eolian_State *eos = eolian_state_new();
+
+   fail_if(!eolian_state_directory_add(eos, TESTS_SRC_DIR"/data"));
+
+   fail_if(!(unit = eolian_state_file_parse(eos, TESTS_SRC_DIR"/data/mixins_require.eo")));
+
+   fail_if (!(cl = eolian_state_class_by_name_get(eos, "Mixins.Require")));
+   fail_if (!(base = eolian_state_class_by_name_get(eos, "Base")));
+
+   ck_assert_ptr_eq(eolian_class_parent_get(cl), NULL);
+
+   //Check that implements is empty
+   {
+      Eolian_Class *extc;
+      Eina_Iterator *ext = eolian_class_extensions_get (cl);
+
+      EINA_ITERATOR_FOREACH(ext, extc)
+        {
+           ck_abort_msg("Iterator should be empty");
+        }
+      eina_iterator_free(ext);
+   }
+   //check that implements contains this one class
+   {
+      Eolian_Implement *impl;
+      Eina_Iterator *i = eolian_class_extensions_get (cl);
+
+      EINA_ITERATOR_FOREACH(i, impl)
+        {
+           ck_assert_ptr_eq(eolian_implement_class_get(impl), base);
+        }
+      eina_iterator_free(i);
+   }
+   eolian_state_free(eos);
+}
+EFL_END_TEST
+
+EFL_START_TEST(eolian_class_requires_classes)
+{
+   const Eolian_Unit *unit;
+   const Eolian_Class *cl;
+
+   Eolian_State *eos = eolian_state_new();
+
+   fail_if(!eolian_state_directory_add(eos, TESTS_SRC_DIR"/data"));
+
+   fail_if(!(unit = eolian_state_file_parse(eos, TESTS_SRC_DIR"/data/class_requires.eo")));
+
+   fail_if (!(cl = eolian_state_class_by_name_get(eos, "Class.Requires")));
+
+   eolian_state_free(eos);
+}
+EFL_END_TEST
+
 void eolian_parsing_test(TCase *tc)
 {
    tcase_add_test(tc, eolian_simple_parsing);
@@ -1575,4 +1635,6 @@ void eolian_parsing_test(TCase *tc)
    tcase_add_test(tc, eolian_function_types);
    tcase_add_test(tc, eolian_function_as_arguments);
    tcase_add_test(tc, eolian_parts);
+   tcase_add_test(tc, eolian_mixins_require);
+   tcase_add_test(tc, eolian_class_requires_classes);
 }