context FEATURE public API for parsing schemas
diff --git a/src/tree_schema.c b/src/tree_schema.c
index 955f8ab..6275235 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -11,9 +11,20 @@
  *
  *     https://opensource.org/licenses/BSD-3-Clause
  */
+#define _DEFAULT_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "libyang.h"
 #include "common.h"
+#include "context.h"
 #include "tree_schema_internal.h"
 
 #define FREE_ARRAY(CTX, ARRAY, FUNC) {uint64_t c__; LY_ARRAY_FOR(ARRAY, c__){FUNC(CTX, LY_ARRAY_INDEX(ARRAY, c__), dict);}free(ARRAY);}
@@ -23,6 +34,13 @@
 static void lysp_grp_free(struct ly_ctx *ctx, struct lysp_grp *grp, int dict);
 static void lysp_node_free(struct ly_ctx *ctx, struct lysp_node *node, int dict);
 
+#define LYSC_CTX_BUFSIZE 4086
+struct lysc_ctx {
+    struct lysc_module *mod;
+    uint16_t path_len;
+    char path[LYSC_CTX_BUFSIZE];
+};
+
 static void
 lysp_stmt_free(struct ly_ctx *ctx, struct lysp_stmt *stmt, int dict)
 {
@@ -518,73 +536,83 @@
 }
 
 API void
-lysc_module_free(struct lysc_module *module)
+lysc_module_free(struct lysc_module *module, void (*private_destructor)(const struct lysc_node *node, void *priv))
 {
     lysc_module_free_(module, 1);
 }
 
-LY_ERR
-lysp_check_prefix(struct ly_parser_ctx *ctx, struct lysp_module *module, const char **value)
+void
+lys_module_free(struct lys_module *module, void (*private_destructor)(const struct lysc_node *node, void *priv))
 {
-    struct lysp_import *i;
+    if (!module) {
+        return;
+    }
 
-    if (module->prefix && &module->prefix != value && !strcmp(module->prefix, *value)) {
-        LOGVAL(ctx->ctx, LY_VLOG_LINE, &ctx->line, LYVE_REFERENCE,
-               "Prefix \"%s\" already used as module prefix.", *value);
-        return LY_EEXIST;
+    lysc_module_free(module->compiled, private_destructor);
+    lysp_module_free(module->parsed);
+    free(module);
+}
+
+static LY_ERR
+lys_compile_iffeature(struct lysc_ctx *ctx, const char *iff_p, struct lysc_iffeature **iffeatures)
+{
+    struct lysc_iffeature *iff;
+
+    if ((ctx->mod->version != 2) && ((iff_p[0] == '(') || strchr(iff_p, ' '))) {
+        LOGVAL(ctx->mod->ctx, LY_VLOG_STR, ctx->path,LY_VCODE_INVAL, strlen(iff_p), iff_p, "if-feature");
+        return LY_EVALID;
     }
-    if (module->imports) {
-        LY_ARRAY_FOR(module->imports, struct lysp_import, i) {
-            if (i->prefix && &i->prefix != value && !strcmp(i->prefix, *value)) {
-                LOGVAL(ctx->ctx, LY_VLOG_LINE, &ctx->line, LYVE_REFERENCE,
-                       "Prefix \"%s\" already used to import \"%s\" module.", *value, i->name);
-                return LY_EEXIST;
-            }
-        }
-    }
+
+    LYSP_ARRAY_NEW_RET(ctx->mod->ctx, *iffeatures, iff, LY_EMEM);
+
     return LY_SUCCESS;
 }
 
 static LY_ERR
-lys_compile_feature(struct ly_ctx *ctx, struct lysp_feature *feature_p, int options, struct lysc_feature **features)
+lys_compile_feature(struct lysc_ctx *ctx, struct lysp_feature *feature_p, int options, struct lysc_feature **features)
 {
     struct lysc_feature *feature;
+    unsigned int u;
+    LY_ERR ret;
 
-    LYSP_ARRAY_NEW_RET(ctx, *features, feature, LY_EMEM);
+    LYSP_ARRAY_NEW_RET(ctx->mod->ctx, *features, feature, LY_EMEM);
 
     if (options & LYSC_OPT_FREE_SP) {
         /* just switch the pointers */
         feature->name = feature_p->name;
     } else {
         /* keep refcounts correct for lysp_module_free() */
-        feature->name = lydict_insert(ctx, feature_p->name, 0);
+        feature->name = lydict_insert(ctx->mod->ctx, feature_p->name, 0);
     }
     feature->flags = feature_p->flags;
 
+    for (u = 0; feature_p->iffeatures && feature_p->iffeatures[u]; ++u) {
+        ret = lys_compile_iffeature(ctx, feature_p->iffeatures[u], &feature->iffeatures);
+        LY_CHECK_RET(ret);
+    }
+
     return LY_SUCCESS;
 }
 
 LY_ERR
 lys_compile(struct lysp_module *sp, int options, struct lysc_module **sc)
 {
-    /* shortcuts */
-    struct ly_ctx *ctx;
+    struct lysc_ctx ctx = {0};
     struct lysc_module *mod_c;
     void *p;
-
     LY_ERR ret;
 
     LY_CHECK_ARG_RET(NULL, sc, sp, sp->ctx, LY_EINVAL);
-    ctx = sp->ctx;
 
     if (sp->submodule) {
-        LOGERR(ctx, LY_EINVAL, "Submodules (%s) are not supposed to be compiled, compile only the main modules.", sp->name);
+        LOGERR(sp->ctx, LY_EINVAL, "Submodules (%s) are not supposed to be compiled, compile only the main modules.", sp->name);
         return LY_EINVAL;
     }
 
-    mod_c = calloc(1, sizeof *mod_c);
-    LY_CHECK_ERR_RET(!mod_c, LOGMEM(ctx), LY_EMEM);
-    mod_c->ctx = ctx;
+    ctx.mod = mod_c = calloc(1, sizeof *mod_c);
+    LY_CHECK_ERR_RET(!mod_c, LOGMEM(sp->ctx), LY_EMEM);
+    mod_c->ctx = sp->ctx;
+    mod_c->version = sp->version;
 
     if (options & LYSC_OPT_FREE_SP) {
         /* just switch the pointers */
@@ -593,14 +621,14 @@
         mod_c->prefix = sp->prefix;
     } else {
         /* keep refcounts correct for lysp_module_free() */
-        mod_c->name = lydict_insert(ctx, sp->name, 0);
-        mod_c->ns = lydict_insert(ctx, sp->ns, 0);
-        mod_c->prefix = lydict_insert(ctx, sp->prefix, 0);
+        mod_c->name = lydict_insert(sp->ctx, sp->name, 0);
+        mod_c->ns = lydict_insert(sp->ctx, sp->ns, 0);
+        mod_c->prefix = lydict_insert(sp->ctx, sp->prefix, 0);
     }
 
     if (sp->features) {
         LY_ARRAY_FOR(sp->features, struct lysp_feature, p) {
-            ret = lys_compile_feature(ctx, p, options, &mod_c->features);
+            ret = lys_compile_feature(&ctx, p, options, &mod_c->features);
             LY_CHECK_GOTO(ret != LY_SUCCESS, error);
         }
     }
@@ -621,3 +649,185 @@
     }
     return ret;
 }
+
+const struct lys_module *
+lys_parse_mem_(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format, const char *revision, int implement)
+{
+    struct lys_module *mod = NULL;
+    LY_ERR ret;
+
+    LY_CHECK_ARG_RET(ctx, ctx, data, NULL);
+
+    mod = calloc(1, sizeof *mod);
+    LY_CHECK_ERR_RET(!mod, LOGMEM(ctx), NULL);
+
+    switch (format) {
+    case LYS_IN_YIN:
+        /* TODO not yet supported
+        mod = yin_read_module(ctx, data, revision, implement);
+        */
+        break;
+    case LYS_IN_YANG:
+        ret = yang_parse(ctx, data, &mod->parsed);
+        LY_CHECK_RET(ret, NULL);
+        break;
+    default:
+        LOGERR(ctx, LY_EINVAL, "Invalid schema input format.");
+        break;
+    }
+
+    if (implement) {
+        mod->parsed->implemented = 1;
+    }
+
+    if (revision) {
+        /* check revision of the parsed model */
+        if (!mod->parsed->revs || strcmp(revision, LY_ARRAY_INDEX(mod->parsed->revs, 0, struct lysp_revision)->rev)) {
+            LOGERR(ctx, LY_EINVAL, "Module \"%s\" parsed with the wrong revision (\"%s\" instead \"%s\").",
+                   mod->parsed->name, LY_ARRAY_INDEX(mod->parsed->revs, 0, struct lysp_revision)->rev, revision);
+            lysp_module_free(mod->parsed);
+            free(mod);
+            return NULL;
+        }
+    }
+
+    /* check for duplicity in the context */
+
+    /* add into context */
+    ly_set_add(&ctx->list, mod, LY_SET_OPT_USEASLIST);
+
+#if 0
+    /* hack for NETCONF's edit-config's operation attribute. It is not defined in the schema, but since libyang
+     * implements YANG metadata (annotations), we need its definition. Because the ietf-netconf schema is not the
+     * internal part of libyang, we cannot add the annotation into the schema source, but we do it here to have
+     * the anotation definitions available in the internal schema structure. There is another hack in schema
+     * printers to do not print this internally added annotation. */
+    if (mod && ly_strequal(mod->name, "ietf-netconf", 0)) {
+        if (lyp_add_ietf_netconf_annotations(mod)) {
+            lys_free(mod, NULL, 1, 1);
+            return NULL;
+        }
+    }
+#endif
+
+    return mod;
+}
+
+API const struct lys_module *
+lys_parse_mem(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format)
+{
+    return lys_parse_mem_(ctx, data, format, NULL, 1);
+}
+
+static void
+lys_parse_set_filename(struct ly_ctx *ctx, const char **filename, int fd)
+{
+#ifdef __APPLE__
+    char path[MAXPATHLEN];
+#else
+    int len;
+    char path[PATH_MAX], proc_path[32];
+#endif
+
+#ifdef __APPLE__
+    if (fcntl(fd, F_GETPATH, path) != -1) {
+        *filename = lydict_insert(ctx, path, 0);
+    }
+#else
+    /* get URI if there is /proc */
+    sprintf(proc_path, "/proc/self/fd/%d", fd);
+    if ((len = readlink(proc_path, path, PATH_MAX - 1)) > 0) {
+        *filename = lydict_insert(ctx, path, len);
+    }
+#endif
+}
+
+const struct lys_module *
+lys_parse_fd_(struct ly_ctx *ctx, int fd, LYS_INFORMAT format, const char *revision, int implement)
+{
+    const struct lys_module *mod;
+    size_t length;
+    char *addr;
+
+    LY_CHECK_ARG_RET(ctx, ctx, NULL);
+    if (fd < 0) {
+        LOGARG(ctx, fd);
+        return NULL;
+    }
+
+    LY_CHECK_RET(ly_mmap(ctx, fd, &length, (void **)&addr), NULL);
+    if (!addr) {
+        LOGERR(ctx, LY_EINVAL, "Empty schema file.");
+        return NULL;
+    }
+
+    mod = lys_parse_mem_(ctx, addr, format, revision, implement);
+    ly_munmap(addr, length);
+
+    if (mod && !mod->parsed->filepath) {
+        lys_parse_set_filename(ctx, &mod->parsed->filepath, fd);
+    }
+
+    return mod;
+}
+
+API const struct lys_module *
+lys_parse_fd(struct ly_ctx *ctx, int fd, LYS_INFORMAT format)
+{
+    return lys_parse_fd_(ctx, fd, format, NULL, 1);
+}
+
+API const struct lys_module *
+lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format)
+{
+    int fd;
+    const struct lys_module *mod;
+    const char *rev, *dot, *filename;
+    size_t len;
+
+    LY_CHECK_ARG_RET(ctx, ctx, path, NULL);
+
+    fd = open(path, O_RDONLY);
+    LY_CHECK_ERR_RET(fd == -1, LOGERR(ctx, LY_ESYS, "Opening file \"%s\" failed (%s).", path, strerror(errno)), NULL);
+
+    mod = lys_parse_fd(ctx, fd, format);
+    close(fd);
+    LY_CHECK_RET(!mod, NULL);
+
+    /* check that name and revision match filename */
+    filename = strrchr(path, '/');
+    if (!filename) {
+        filename = path;
+    } else {
+        filename++;
+    }
+    rev = strchr(filename, '@');
+    dot = strrchr(filename, '.');
+
+    /* name */
+    len = strlen(mod->parsed->name);
+    if (strncmp(filename, mod->parsed->name, len) ||
+            ((rev && rev != &filename[len]) || (!rev && dot != &filename[len]))) {
+        LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, mod->parsed->name);
+    }
+    if (rev) {
+        len = dot - ++rev;
+        if (!mod->parsed->revs || len != 10 || strncmp(LY_ARRAY_INDEX(mod->parsed->revs, 0, struct lysp_revision)->rev, rev, len)) {
+            LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename,
+                   mod->parsed->revs ? LY_ARRAY_INDEX(mod->parsed->revs, 0, struct lysp_revision)->rev : "none");
+        }
+    }
+
+    if (!mod->parsed->filepath) {
+        /* store URI */
+        char rpath[PATH_MAX];
+        if (realpath(path, rpath) != NULL) {
+            mod->parsed->filepath = lydict_insert(ctx, rpath, 0);
+        } else {
+            mod->parsed->filepath = lydict_insert(ctx, path, 0);
+        }
+    }
+
+    return mod;
+}
+