parser FEATURE added collision check for grouping

Collision checking of names is similar to typedef checking.
diff --git a/src/parser_yang.c b/src/parser_yang.c
index 8147a7f..b62c54a 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -2977,7 +2977,7 @@
     grp->nodetype = LYS_GROUPING;
     grp->parent = parent;
 
-    YANG_READ_SUBSTMT_FOR(ctx, kw, word, word_len, ret, return LY_SUCCESS, return ret) {
+    YANG_READ_SUBSTMT_FOR(ctx, kw, word, word_len, ret, goto checks, return ret) {
         switch (kw) {
         case LY_STMT_DESCRIPTION:
             LY_CHECK_RET(parse_text_field(ctx, LY_STMT_DESCRIPTION, 0, &grp->dsc, Y_STR_ARG, &grp->exts));
@@ -3036,6 +3036,13 @@
             return LY_EVALID;
         }
     }
+    LY_CHECK_RET(ret);
+checks:
+    /* store data for collision check */
+    if (parent) {
+        assert(ctx->main_ctx);
+        LY_CHECK_RET(ly_set_add(&ctx->main_ctx->grps_nodes, parent, 0, NULL));
+    }
 
     return ret;
 }
diff --git a/src/parser_yin.c b/src/parser_yin.c
index c2d963a..63c7661 100644
--- a/src/parser_yin.c
+++ b/src/parser_yin.c
@@ -2327,6 +2327,12 @@
     ret = yin_parse_content(ctx, subelems, subelems_size, LY_STMT_GROUPING, NULL, &grp->exts);
     subelems_deallocator(subelems_size, subelems);
 
+    /* store data for collision check */
+    if (!ret && grp->parent) {
+        assert(ctx->main_ctx);
+        LY_CHECK_RET(ly_set_add(&ctx->main_ctx->grps_nodes, grp->parent, 0, NULL));
+    }
+
     return ret;
 }
 
diff --git a/src/tree_schema.c b/src/tree_schema.c
index 4603fb8..3c95a90 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -1569,7 +1569,7 @@
 
     /* check name collisions */
     LY_CHECK_GOTO(ret = lysp_check_dup_typedefs(pctx, mod->parsed), cleanup);
-    /* TODO groupings */
+    LY_CHECK_GOTO(ret = lysp_check_dup_groupings(pctx, mod->parsed), cleanup);
     LY_CHECK_GOTO(ret = lysp_check_dup_features(pctx, mod->parsed), cleanup);
     LY_CHECK_GOTO(ret = lysp_check_dup_identities(pctx, mod->parsed), cleanup);
 
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index 3b510aa..0658a33 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -137,6 +137,22 @@
     return NULL;
 }
 
+static const struct lysp_node_grp *
+lysp_grouping_match(const char *name, struct lysp_node *node)
+{
+    const struct lysp_node_grp *groupings, *grp_iter;
+
+    groupings = lysp_node_groupings(node);
+    LY_LIST_FOR(groupings, grp_iter) {
+        if (!strcmp(name, grp_iter->name)) {
+            /* match */
+            return grp_iter;
+        }
+    }
+
+    return NULL;
+}
+
 static LY_DATA_TYPE
 lysp_type_str2builtin(const char *name, size_t len)
 {
@@ -465,6 +481,104 @@
     return ret;
 }
 
+/**
+ * @brief Check name of a new grouping to avoid name collisions.
+ *
+ * @param[in] ctx Parser context, module where the grouping is being defined is taken from here.
+ * @param[in] node Schema node where the grouping is being defined, NULL in case of a top-level grouping.
+ * @param[in] grp Grouping definition to check.
+ * @param[in,out] grps_global Initialized hash table to store temporary data between calls. When the module's
+ * groupings are checked, caller is supposed to free the table.
+ * @return LY_EVALID in case of collision, LY_SUCCESS otherwise.
+ */
+static LY_ERR
+lysp_check_dup_grouping(struct lys_parser_ctx *ctx, struct lysp_node *node, const struct lysp_node_grp *grp,
+        struct hash_table *grps_global)
+{
+    struct lysp_node *parent;
+    uint32_t hash;
+    size_t name_len;
+    const char *name;
+    const struct lysp_node_grp *groupings, *grp_iter;
+
+    assert(ctx);
+    assert(grp);
+
+    name = grp->name;
+    name_len = strlen(name);
+
+    /* check locally scoped groupings (avoid name shadowing) */
+    if (node) {
+        groupings = lysp_node_groupings(node);
+        LY_LIST_FOR(groupings, grp_iter) {
+            if (grp_iter == grp) {
+                break;
+            }
+            if (!strcmp(name, grp_iter->name)) {
+                LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG,
+                        "Duplicate identifier \"%s\" of grouping statement - name collision with sibling grouping.", name);
+                return LY_EVALID;
+            }
+        }
+        /* search grouping in parent's nodes */
+        for (parent = node->parent; parent; parent = parent->parent) {
+            if (lysp_grouping_match(name, parent)) {
+                LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG,
+                        "Duplicate identifier \"%s\" of grouping statement - name collision with another scoped grouping.", name);
+                return LY_EVALID;
+            }
+        }
+    }
+
+    /* check collision with the top-level groupings */
+    if (node) {
+        hash = dict_hash(name, name_len);
+        if (!lyht_find(grps_global, &name, hash, NULL)) {
+            LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG,
+                    "Duplicate identifier \"%s\" of grouping statement - scoped grouping collide with a top-level grouping.", name);
+            return LY_EVALID;
+        }
+    } else {
+        LY_CHECK_RET(lysp_check_dup_ht_insert(ctx, grps_global, name, "grouping",
+                "name collision with another top-level grouping"));
+    }
+
+    return LY_SUCCESS;
+}
+
+LY_ERR
+lysp_check_dup_groupings(struct lys_parser_ctx *ctx, struct lysp_module *mod)
+{
+    struct hash_table *ids_global;
+    const struct lysp_node_grp *groupings, *grp_iter;
+    LY_ARRAY_COUNT_TYPE u;
+    uint32_t i;
+    LY_ERR ret = LY_SUCCESS;
+
+    ids_global = lyht_new(LYHT_MIN_SIZE, sizeof(char *), lysp_id_cmp, NULL, 1);
+    LY_LIST_FOR(mod->groupings, grp_iter) {
+        ret = lysp_check_dup_grouping(ctx, NULL, grp_iter, ids_global);
+        LY_CHECK_GOTO(ret, cleanup);
+    }
+    LY_ARRAY_FOR(mod->includes, u) {
+        LY_LIST_FOR(mod->includes[u].submodule->groupings, grp_iter) {
+            ret = lysp_check_dup_grouping(ctx, NULL, grp_iter, ids_global);
+            LY_CHECK_GOTO(ret, cleanup);
+        }
+    }
+    for (i = 0; i < ctx->grps_nodes.count; ++i) {
+        groupings = lysp_node_groupings((struct lysp_node *)ctx->grps_nodes.objs[i]);
+        LY_LIST_FOR(groupings, grp_iter) {
+            ret = lysp_check_dup_grouping(ctx, (struct lysp_node *)ctx->grps_nodes.objs[i], grp_iter, ids_global);
+            LY_CHECK_GOTO(ret, cleanup);
+        }
+    }
+
+cleanup:
+    lyht_free(ids_global);
+    return ret;
+}
+
 static ly_bool
 ly_ptrequal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data))
 {
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index 351290f..e2d0f6b 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -147,7 +147,7 @@
     struct ly_set tpdfs_nodes;       /**< Set of nodes that contain typedef(s). Invalid in case of
                                           submodule, use ::lys_parser_ctx.main_ctx instead. */
     struct ly_set grps_nodes;        /**< Set of nodes that contain grouping(s). Invalid in case of
-                                          submodule, use ::lys_parser_ctx.main_ctx instead. TODO implement. */
+                                          submodule, use ::lys_parser_ctx.main_ctx instead. */
     struct lysp_module *parsed_mod;  /**< (sub)module being parsed */
     struct lys_parser_ctx *main_ctx; /**< This pointer must not be NULL. If this context deals with the submodule,
                                           then should be set to the context of the module to which it belongs,
@@ -162,7 +162,7 @@
     struct ly_set tpdfs_nodes;       /**< Set of nodes that contain typedef(s). Invalid in case of
                                           submodule, use ::lys_parser_ctx.main_ctx instead. */
     struct ly_set grps_nodes;        /**< Set of nodes that contain grouping(s). Invalid in case of
-                                          submodule, use ::lys_parser_ctx.main_ctx instead. TODO implement. */
+                                          submodule, use ::lys_parser_ctx.main_ctx instead. */
     struct lysp_module *parsed_mod;  /**< (sub)module being parsed */
     struct lys_parser_ctx *main_ctx; /**< This pointer must not be NULL. If this context deals with the submodule,
                                           then should be set to the context of the module to which it belongs,
@@ -185,7 +185,7 @@
     struct ly_set tpdfs_nodes;       /**< Set of nodes that contain typedef(s). Invalid in case of
                                           submodule, use ::lys_parser_ctx.main_ctx instead. */
     struct ly_set grps_nodes;        /**< Set of nodes that contain grouping(s). Invalid in case of
-                                          submodule, use ::lys_parser_ctx.main_ctx instead. TODO implement. */
+                                          submodule, use ::lys_parser_ctx.main_ctx instead. */
     struct lysp_module *parsed_mod;  /**< (sub)module being parsed */
     struct lys_parser_ctx *main_ctx; /**< This pointer must not be NULL. If this context deals with the submodule,
                                           then should be set to the context of the module to which it belongs,
@@ -257,6 +257,15 @@
 LY_ERR lysp_check_dup_typedefs(struct lys_parser_ctx *ctx, struct lysp_module *mod);
 
 /**
+ * @brief Check names of groupings in the parsed module to detect collisions.
+ *
+ * @param[in] ctx Parser context for logging and to maintain grps_nodes.
+ * @param[in] mod Module where the type is being defined.
+ * @return LY_ERR value.
+ */
+LY_ERR lysp_check_dup_groupings(struct lys_parser_ctx *ctx, struct lysp_module *mod);
+
+/**
  * @brief Check names of features in the parsed module and submodules to detect collisions.
  *
  * @param[in] ctx Parser context.