logger CHANGE support for validation errors - work in progress
diff --git a/src/common.h b/src/common.h
index 7e7801a..c73eec9 100644
--- a/src/common.h
+++ b/src/common.h
@@ -47,12 +47,22 @@
     ILO_ERR2WRN, /* change errors to warnings */
 };
 
+enum LY_VLOG_ELEM {
+    LY_VLOG_NONE = 0,
+    LY_VLOG_LINE,/* line number */
+    LY_VLOG_LYS, /* struct lysc_node* */
+    LY_VLOG_LYD, /* struct lyd_node* */
+    LY_VLOG_STR, /* const char* */
+    LY_VLOG_PREV /* use exact same previous path */
+};
+
 extern THREAD_LOCAL enum int_log_opts log_opt;
 extern volatile uint8_t ly_log_level;
 extern volatile uint8_t ly_log_opts;
 
 void ly_err_free(void *ptr);
 void ly_log(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, const char *format, ...);
+void ly_vlog(const struct ly_ctx *ctx, enum LY_VLOG_ELEM elem_type, const void *elem, LY_VECODE code, const char *format, ...);
 
 #define LOGERR(ctx, errno, str, args...) ly_log(ctx, LY_LLERR, errno, str, ##args)
 #define LOGWRN(ctx, str, args...) ly_log(ctx, LY_LLWRN, 0, str, ##args)
@@ -68,6 +78,11 @@
 #define LOGMEM(CTX) LOGERR(CTX, LY_EMEM, "Memory allocation failed (%s()).", __func__)
 #define LOGINT(CTX) LOGERR(CTX, LY_EINT, "Internal error (%s:%d).", __FILE__, __LINE__)
 #define LOGARG(CTX, ARG) LOGERR(CTX, LY_EINVAL, "Invalid argument %s (%s()).", #ARG, __func__)
+#define LOGVAL(CTX, ELEM_TYPE, ELEM, CODE, FORMAT, args...) ly_vlog(CTX, ELEM_TYPE, ELEM, FORMAT ##args)
+
+#define LOGMEM_RET(CTX) LOGMEM(CTX); return LY_EMEM
+#define LOGINT_RET(CTX) LOGINT(CTX); return LY_EINT
+#define LOGARG_RET(CTX) LOGARG(CTX); return LY_EINVAL
 
 /*
  * Common code to check return value and perform appropriate action.
@@ -87,6 +102,13 @@
 #define LY_CHECK_ARG_RET3(CTX, ARG1, ARG2, ARG3, RETVAL) LY_CHECK_ARG_RET2(CTX, ARG1, ARG2, RETVAL);LY_CHECK_ARG_RET1(CTX, ARG3, RETVAL)
 #define LY_CHECK_ARG_RET(CTX, ...) GETMACRO4(__VA_ARGS__, LY_CHECK_ARG_RET3, LY_CHECK_ARG_RET2, LY_CHECK_ARG_RET1)(CTX, __VA_ARGS__)
 
+#define LY_VCODE_MISSING     LYVE_SYNTAX, "Missing %s \"%s\"."
+#define LY_VCODE_INVAL       LYVE_SYNTAX, "Invalid %s."
+#define LY_VCODE_INCHAR      LYVE_SYNTAX, "Encountered invalid character sequence \"%.10s\"."
+#define LY_VCODE_EOF         LYVE_SYNTAX, "Unexpected end of input data."
+#define LY_VCODE_INSTMT      LYVE_SYNTAX_YANG, "Invalid keyword \"%s\"."
+#define LY_VCODE_INCHILDSTMT LYVE_SYNTAX_YANG, "Invalid keyword \"%s\" as a child to \"%s\"."
+
 /*
  * If the compiler supports attribute to mark objects as hidden, mark all
  * objects as hidden and export only objects explicitly marked to be part of
diff --git a/src/log.c b/src/log.c
index d61895e..d1a6dd3 100644
--- a/src/log.c
+++ b/src/log.c
@@ -30,6 +30,9 @@
 volatile int ly_log_dbg_groups = 0;
 #endif
 
+/* how many bytes add when enlarging buffers */
+#define LY_BUF_STEP 128
+
 API LY_VECODE
 ly_vecode(const struct ly_ctx *ctx)
 {
@@ -369,6 +372,259 @@
     va_end(ap);
 }
 
+static LY_ERR
+ly_vlog_build_path_print(const struct ly_ctx *ctx, char **path, uint16_t *index, const char *str, uint16_t str_len, uint16_t *length)
+{
+    void *mem;
+    uint16_t step;
+
+    if ((*index) < str_len) {
+        /* enlarge buffer */
+        step = (str_len < LY_BUF_STEP) ? LY_BUF_STEP : str_len;
+        mem = realloc(*path, *length + *index + step + 1);
+        LY_CHECK_ERR_RET(!mem, LOGMEM(ctx), LY_EMEM);
+        *path = mem;
+
+        /* move data, lengths */
+        memmove(&(*path)[*index + step], &(*path)[*index], *length);
+        (*index) += step;
+    }
+
+    (*index) -= str_len;
+    memcpy(&(*path)[*index], str, str_len);
+    *length += str_len;
+
+    return 0;
+}
+
+LY_ERR
+ly_vlog_build_path(const struct ly_ctx *ctx, enum LY_VLOG_ELEM elem_type, const void *elem, char **path, int UNUSED(schema_all_prefixes))
+{
+    uint16_t length, index;
+    size_t len;
+    LY_ERR rc = LY_SUCCESS;
+
+    length = 0;
+    *path = malloc(1);
+    LY_CHECK_ERR_RET(!(*path), LOGMEM(ctx), LY_EMEM);
+    index = 0;
+
+    while (elem) {
+        switch (elem_type) {
+        case LY_VLOG_LYS:
+            /* TODO */
+            break;
+        case LY_VLOG_LYD:
+#if 0 /* TODO when data tree present */
+            name = ((struct lyd_node *)elem)->schema->name;
+            if (!((struct lyd_node *)elem)->parent ||
+                    lyd_node_module((struct lyd_node *)elem) != lyd_node_module(((struct lyd_node *)elem)->parent)) {
+                prefix = lyd_node_module((struct lyd_node *)elem)->name;
+            } else {
+                prefix = NULL;
+            }
+
+            /* handle predicates (keys) in case of lists */
+            if (((struct lyd_node *)elem)->schema->nodetype == LYS_LIST) {
+                dlist = (struct lyd_node *)elem;
+                slist = (struct lys_node_list *)((struct lyd_node *)elem)->schema;
+                if (slist->keys_size) {
+                    /* schema list with keys - use key values in predicates */
+                    for (i = slist->keys_size - 1; i > -1; i--) {
+                        LY_TREE_FOR(dlist->child, diter) {
+                            if (diter->schema == (struct lys_node *)slist->keys[i]) {
+                                break;
+                            }
+                        }
+                        if (diter && ((struct lyd_node_leaf_list *)diter)->value_str) {
+                            if (strchr(((struct lyd_node_leaf_list *)diter)->value_str, '\'')) {
+                                val_start = "=\"";
+                                val_end = "\"]";
+                            } else {
+                                val_start = "='";
+                                val_end = "']";
+                            }
+
+                            /* print value */
+                            if (ly_vlog_build_path_print(path, &index, val_end, 2, &length)) {
+                                return -1;
+                            }
+                            len = strlen(((struct lyd_node_leaf_list *)diter)->value_str);
+                            if (ly_vlog_build_path_print(path, &index,
+                                    ((struct lyd_node_leaf_list *)diter)->value_str, len, &length)) {
+                                return -1;
+                            }
+
+                            /* print schema name */
+                            if (ly_vlog_build_path_print(path, &index, val_start, 2, &length)) {
+                                return -1;
+                            }
+                            len = strlen(diter->schema->name);
+                            if (ly_vlog_build_path_print(path, &index, diter->schema->name, len, &length)) {
+                                return -1;
+                            }
+
+                            if (lyd_node_module(dlist) != lyd_node_module(diter)) {
+                                if (ly_vlog_build_path_print(path, &index, ":", 1, &length)) {
+                                    return -1;
+                                }
+                                len = strlen(lyd_node_module(diter)->name);
+                                if (ly_vlog_build_path_print(path, &index, lyd_node_module(diter)->name, len, &length)) {
+                                    return -1;
+                                }
+                            }
+
+                            if (ly_vlog_build_path_print(path, &index, "[", 1, &length)) {
+                                return -1;
+                            }
+                        }
+                    }
+                } else {
+                    /* schema list without keys - use instance position */
+                    i = j = lyd_list_pos(dlist);
+                    len = 1;
+                    while (j > 9) {
+                        ++len;
+                        j /= 10;
+                    }
+
+                    if (ly_vlog_build_path_print(path, &index, "]", 1, &length)) {
+                        return -1;
+                    }
+
+                    str = malloc(len + 1);
+                    LY_CHECK_ERR_RETURN(!str, LOGMEM(NULL), -1);
+                    sprintf(str, "%d", i);
+
+                    if (ly_vlog_build_path_print(path, &index, str, len, &length)) {
+                        free(str);
+                        return -1;
+                    }
+                    free(str);
+
+                    if (ly_vlog_build_path_print(path, &index, "[", 1, &length)) {
+                        return -1;
+                    }
+                }
+            } else if (((struct lyd_node *)elem)->schema->nodetype == LYS_LEAFLIST &&
+                    ((struct lyd_node_leaf_list *)elem)->value_str) {
+                if (strchr(((struct lyd_node_leaf_list *)elem)->value_str, '\'')) {
+                    val_start = "[.=\"";
+                    val_end = "\"]";
+                } else {
+                    val_start = "[.='";
+                    val_end = "']";
+                }
+
+                if (ly_vlog_build_path_print(path, &index, val_end, 2, &length)) {
+                    return -1;
+                }
+                len = strlen(((struct lyd_node_leaf_list *)elem)->value_str);
+                if (ly_vlog_build_path_print(path, &index, ((struct lyd_node_leaf_list *)elem)->value_str, len, &length)) {
+                    return -1;
+                }
+                if (ly_vlog_build_path_print(path, &index, val_start, 4, &length)) {
+                    return -1;
+                }
+            }
+
+            /* check if it is yang-data top element */
+            if (!((struct lyd_node *)elem)->parent) {
+                ext_name = lyp_get_yang_data_template_name(elem);
+                if (ext_name) {
+                    if (ly_vlog_build_path_print(path, &index, name, strlen(name), &length)) {
+                        return -1;
+                    }
+                    if (ly_vlog_build_path_print(path, &index, "/", 1, &length)) {
+                        return -1;
+                    }
+                    yang_data_extension = 1;
+                    name = ext_name;
+               }
+            }
+
+            elem = ((struct lyd_node *)elem)->parent;
+            break;
+#endif
+        case LY_VLOG_STR:
+            len = strlen((const char *)elem);
+            rc = ly_vlog_build_path_print(ctx, path, &index, (const char *)elem, len, &length);
+            LY_CHECK_RET(rc != LY_SUCCESS, rc);
+            goto success;
+        case LY_VLOG_LINE:
+
+            goto success;
+        default:
+            /* shouldn't be here */
+            LOGINT_RET(ctx);
+        }
+
+#if 0 /* TODO when data/schema tree present */
+        if (name) {
+            if (ly_vlog_build_path_print(ctx, path, &index, name, strlen(name), &length)) {
+                return -1;
+            }
+            if (prefix) {
+                if (yang_data_extension && ly_vlog_build_path_print(path, &index, "#", 1, &length)) {
+                    return -1;
+                }
+                if (ly_vlog_build_path_print(ctx, path, &index, ":", 1, &length)) {
+                    return -1;
+                }
+                if (ly_vlog_build_path_print(ctx, path, &index, prefix, strlen(prefix), &length)) {
+                    return -1;
+                }
+            }
+        }
+        if (ly_vlog_build_path_print(ctx, path, &index, "/", 1, &length)) {
+            return -1;
+        }
+        if ((elem_type == LY_VLOG_LYS) && !elem && sparent && (sparent->nodetype == LYS_AUGMENT)) {
+            len = strlen(((struct lys_node_augment *)sparent)->target_name);
+            if (ly_vlog_build_path_print(ctx, path, &index, ((struct lys_node_augment *)sparent)->target_name, len, &length)) {
+                return -1;
+            }
+        }
+#endif
+    }
+
+success:
+    memmove(*path, (*path) + index, length);
+    (*path)[length] = '\0';
+    return LY_SUCCESS;
+}
+
+void
+ly_vlog(const struct ly_ctx *ctx, enum LY_VLOG_ELEM elem_type, const void *elem, LY_VECODE code, const char *format, ...)
+{
+    va_list ap;
+    char* path = NULL;
+    const struct ly_err_item *first;
+
+    if (path_flag && (elem_type != LY_VLOG_NONE)) {
+        if (elem_type == LY_VLOG_PREV) {
+            /* use previous path */
+            first = ly_err_first(ctx);
+            if (first && first->prev->path) {
+                path = strdup(first->prev->path);
+            }
+        } else {
+            /* print path */
+            if (!elem) {
+                /* top-level */
+                path = strdup("/");
+            } else {
+                ly_vlog_build_path(ctx, elem_type, elem, &path, 0);
+            }
+        }
+    }
+
+    va_start(ap, format);
+    log_vprintf(ctx, LY_LLERR, LY_EVALID, code, path, format, ap);
+    /* path is spent and should not be freed! */
+    va_end(ap);
+}
+
 API void
 ly_err_print(struct ly_err_item *eitem)
 {
diff --git a/src/log.h b/src/log.h
index 8ab9703..d1b159e 100644
--- a/src/log.h
+++ b/src/log.h
@@ -159,6 +159,8 @@
  */
 typedef enum {
     LYVE_SUCCESS = 0,  /**< no error */
+    LYVE_SYNTAX,       /**< generic syntax error */
+    LYVE_SYNTAX_YANG,  /**< YANG-related syntax error */
 } LY_VECODE;
 
 /**