tree FEATURE lys_parse_instance_predicate()

TODO: not yet tested!
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index f38aeec..297bf35 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -92,6 +92,102 @@
 }
 
 LY_ERR
+lys_parse_instance_predicate(const char **pred, size_t limit,
+                             const char **prefix, size_t *prefix_len, const char **id, size_t *id_len, const char **value, size_t *value_len,
+                             const char **errmsg)
+{
+    LY_ERR ret = LY_EVALID;
+    const char *in = *pred;
+    size_t offset = 1;
+    int expr = 0;
+    char quot;
+
+    assert(in[0] == '\[');
+
+    *prefix = *id = *value = NULL;
+    *prefix_len = *id_len = *value_len = 0;
+
+    /* leading *WSP */
+    for (; isspace(in[offset]); offset++);
+
+    if (isdigit(in[offset])) {
+        /* pos: "[" *WSP positive-integer-value *WSP "]" */
+        if (in[offset] == '0') {
+            /* zero */
+            *errmsg = "The position predicate cannot be zero.";
+            goto error;
+        }
+
+        /* positive-integer-value */
+        *id = &in[offset++];
+        for (; isdigit(in[offset]); offset++);
+        *id_len = &in[offset] - *id;
+
+    } else if (in[offset] == '.') {
+        /* leaf-list-predicate: "[" *WSP "." *WSP "=" *WSP quoted-string *WSP "]" */
+        *id = &in[offset];
+        *id_len = 1;
+        offset++;
+        expr = 1;
+
+    } else {
+        /* key-predicate: "[" *WSP node-identifier *WSP "=" *WSP quoted-string *WSP "]" */
+        in = &in[offset];
+        if (lys_parse_nodeid(&in, prefix, prefix_len, id, id_len)) {
+            *errmsg = "Invalid node-identifier.";
+            goto error;
+        }
+        offset = in - *pred;
+        in = *pred;
+        expr = 1;
+    }
+
+    if (expr) {
+        /*  *WSP "=" *WSP quoted-string *WSP "]" */
+        for (; isspace(in[offset]); offset++);
+
+        if (in[offset] != '=') {
+            *errmsg = "Unexpected character instead of \'=\'.";
+            goto error;
+        }
+        offset++;
+        for (; isspace(in[offset]); offset++);
+
+        /* quoted-string */
+        quot = in[offset++];
+        if (quot != '\'' && quot != '\"') {
+            *errmsg = "String value is not quoted.";
+            goto error;
+        }
+        *value = &in[offset];
+        for (;offset < limit && in[offset] != quot; offset++);
+        *value_len = &in[offset] - *value;
+    }
+
+    /* *WSP "]" */
+    for(; isspace(in[offset]); offset++);
+    if (in[offset] != ']') {
+        *errmsg = "Predicate is not terminated by \']\' character.";
+        goto error;
+    }
+
+    if (offset < limit) {
+        return LY_SUCCESS;
+    }
+
+    /* we read after the limit */
+    *errmsg = "Predicate is incomplete.";
+    *prefix = *id = *value = NULL;
+    *prefix_len = *id_len = *value_len = 0;
+    offset = limit;
+    ret = LY_EINVAL;
+
+error:
+    *pred = &in[offset];
+    return ret;
+}
+
+LY_ERR
 lys_resolve_schema_nodeid(struct lysc_ctx *ctx, const char *nodeid, size_t nodeid_len, const struct lysc_node *context_node,
                           const struct lys_module *context_module, int nodetype, int implement,
                           const struct lysc_node **target, uint16_t *result_flag)
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index 2ef0817..b01a496 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -339,6 +339,28 @@
                                  const struct lysc_node **target, uint16_t *result_flag);
 
 /**
+ * @brief parse instance-identifier's predicate, supports key-predicate, leaf-list-predicate and pos rules from YANG ABNF Grammar.
+ *
+ * @param[in, out] pred Predicate string (including the leading '[') to parse. The string is updated according to what was parsed
+ * (even for error case, so it can be used to determine which substring caused failure).
+ * @param[in] limit Limiting length of the @p pred. Function expects NULL terminated string which is not overread.
+ * The limit value is not checked with each character, so it can be overread and the failure is detected later.
+ * @param[out] prefix Start of the node-identifier's prefix if any, NULL in case of pos or leaf-list-predicate rules.
+ * @param[out] prefix_len Length of the parsed @p prefix.
+ * @param[out] id Start of the node-identifier's identifier string, NULL in case of pos rule, "." in case of leaf-list-predicate rule.
+ * @param[out] id_len Length of the parsed @p id.
+ * @param[out] value Start of the quoted-string (without quotation marks), NULL in case of pos rule.
+ * @param[out] value_len Length of the parsed @p value.
+ * @param[out] errmsg Error message string in case of error.
+ * @return LY_SUCCESS in case a complete predicate was parsed.
+ * @return LY_EVALID in case of invalid predicate form.
+ * @return LY_EINVAL in case of reaching @p limit when parsing @p pred.
+ */
+LY_ERR lys_parse_instance_predicate(const char **pred, size_t limit,
+                                    const char **prefix, size_t *prefix_len, const char **id, size_t *id_len,
+                                    const char **value, size_t *value_len, const char **errmsg);
+
+/**
  * @brief Find the module referenced by prefix in the provided mod.
  *
  * Reverse function to lys_prefix_find_module().