Further module system revamp (fix #659)

To import a module now use:

    # Import module.jq file:
    import "relative/path/to/module" as foo;

    # Use the module's defs as foo::<def-name>

To import a JSON file:

    # Read file.json:
    import "relative/path/to/file" as $foo;
    #
    # Use as $foo::foo

Using `-L` now drops the builtin library path and appends the requested
path to the empty array (or the result of an earlier `-L`).

Support for the `$JQ_LIBRARY_PATH` environment variable has been
removed.
This commit is contained in:
Nicolas Williams committed 2014-12-31 20:09:53 -06:00
1 parent 7dc34b92af
commit ae7f8d6ab9
13 files changed
+351 -207

No files matched your search

+18
View File
@@ -824,6 +824,21 @@ static jv f_env(jq_state *jq, jv input) {
return env;
}
static jv f_get_search_list(jq_state *jq, jv input) {
jv_free(input);
return jq_get_lib_dirs(jq);
}
static jv f_get_prog_origin(jq_state *jq, jv input) {
jv_free(input);
return jq_get_prog_origin(jq);
}
static jv f_get_jq_origin(jq_state *jq, jv input) {
jv_free(input);
return jq_get_jq_origin(jq);
}
static jv f_string_split(jq_state *jq, jv a, jv b) {
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) {
jv_free(a);
@@ -937,6 +952,9 @@ static const struct cfunction function_list[] = {
{(cfunction_ptr)f_error, "error", 2},
{(cfunction_ptr)f_format, "format", 2},
{(cfunction_ptr)f_env, "env", 1},
{(cfunction_ptr)f_get_search_list, "get_search_list", 1},
{(cfunction_ptr)f_get_prog_origin, "get_prog_origin", 1},
{(cfunction_ptr)f_get_jq_origin, "get_jq_origin", 1},
{(cfunction_ptr)f_match, "_match_impl", 4},
{(cfunction_ptr)f_modulemeta, "modulemeta", 1},
{(cfunction_ptr)f_input, "_input", 1},
+1
View File
@@ -9,6 +9,7 @@
#define NONE 0, 1
#define CONSTANT OP_HAS_CONSTANT, 2
#define VARIABLE (OP_HAS_VARIABLE | OP_HAS_BINDING), 3
#define GLOBAL (OP_HAS_CONSTANT | OP_HAS_VARIABLE | OP_HAS_BINDING | OP_IS_CALL_PSEUDO), 4
#define BRANCH OP_HAS_BRANCH, 2
#define CFUNC (OP_HAS_CFUNC | OP_HAS_BINDING), 3
#define UFUNC (OP_HAS_UFUNC | OP_HAS_BINDING | OP_IS_CALL_PSEUDO), 4
+41 -18
View File
@@ -148,6 +148,15 @@ block gen_const(jv constant) {
return inst_block(i);
}
block gen_const_global(jv constant, const char *name) {
assert((opcode_describe(STORE_GLOBAL)->flags & (OP_HAS_CONSTANT | OP_HAS_VARIABLE | OP_HAS_BINDING)) ==
(OP_HAS_CONSTANT | OP_HAS_VARIABLE | OP_HAS_BINDING));
inst* i = inst_new(STORE_GLOBAL);
i->imm.constant = constant;
i->symbol = strdup(name);
return inst_block(i);
}
int block_is_const(block b) {
return (block_is_single(b) && b.first->op == LOADK);
}
@@ -239,6 +248,10 @@ int block_has_only_binders_and_imports(block binders, int bindflags) {
return 1;
}
static int inst_is_binder(inst *i, int bindflags) {
return !((opcode_describe(i->op)->flags & bindflags) != bindflags && i->op != MODULEMETA);
}
int block_has_only_binders(block binders, int bindflags) {
bindflags |= OP_HAS_BINDING;
bindflags &= ~OP_BIND_WILDCARD;
@@ -311,10 +324,6 @@ static int block_bind_subblock(block binder, block body, int bindflags, int brea
((bindflags & OP_BIND_WILDCARD) && i->symbol[0] == '*' &&
break_distance <= 3 && (i->symbol[1] == '1' + break_distance) &&
i->symbol[2] == '\0'))) {
if (bindflags & OP_BIND_WILDCARD) {
jv_mem_free(i->symbol);
i->symbol = strdup(binder.first->symbol);
}
// bind this instruction
if (i->op == CALL_JQ && i->nactuals == -1)
i->nactuals = block_count_actuals(i->arglist);
@@ -353,7 +362,6 @@ block block_bind(block binder, block body, int bindflags) {
}
block block_bind_library(block binder, block body, int bindflags, const char* libname) {
assert(block_has_only_binders(binder, bindflags));
bindflags |= OP_HAS_BINDING;
int nrefs = 0;
int matchlen = strlen(libname);
@@ -363,13 +371,21 @@ block block_bind_library(block binder, block body, int bindflags, const char* li
strcpy(matchname+matchlen,"::");
matchlen += 2;
}
assert(block_has_only_binders(binder, bindflags));
for (inst *curr = binder.first; curr; curr = curr->next) {
int bindflags2 = bindflags;
char* cname = curr->symbol;
char* tname = malloc(strlen(curr->symbol)+matchlen+1);
strcpy(tname, matchname);
strcpy(tname+matchlen,cname);
// Ew
if ((opcode_describe(curr->op)->flags & (OP_HAS_VARIABLE | OP_HAS_CONSTANT)))
bindflags2 = OP_HAS_VARIABLE | OP_HAS_BINDING;
// This mutation is ugly, even if we undo it
curr->symbol = tname;
nrefs += block_bind_subblock(inst_block(curr), body, bindflags, 0);
nrefs += block_bind_subblock(inst_block(curr), body, bindflags2, 0);
curr->symbol = cname;
free(tname);
}
@@ -440,15 +456,13 @@ jv block_take_imports(block* body) {
jv imports = jv_array();
inst* top = NULL;
if (body->first->op == TOP) {
if (body->first && body->first->op == TOP) {
top = block_take(body);
}
while (body->first && (body->first->op == MODULEMETA || body->first->op == DEPS)) {
inst* dep = block_take(body);
if (dep->op == DEPS) {
jv opts = jv_copy(dep->imm.constant);
opts = jv_object_set(opts,jv_string("name"),jv_string(dep->symbol));
imports = jv_array_append(imports, opts);
imports = jv_array_append(imports, jv_copy(dep->imm.constant));
}
inst_free(dep);
}
@@ -458,13 +472,11 @@ jv block_take_imports(block* body) {
return imports;
}
block gen_module(const char* name, block metadata) {
block gen_module(block metadata) {
inst* i = inst_new(MODULEMETA);
i->symbol = strdup(name);
i->imm.constant = block_const(metadata);
if (jv_get_kind(i->imm.constant) != JV_KIND_OBJECT)
i->imm.constant = jv_object_set(jv_object(), jv_string("metadata"), i->imm.constant);
i->imm.constant = jv_object_set(i->imm.constant, jv_string("name"), jv_string(name));
block_free(metadata);
return inst_block(i);
}
@@ -475,17 +487,17 @@ jv block_module_meta(block b) {
return jv_null();
}
block gen_import(const char* name, block metadata, const char* as) {
block gen_import(const char* name, block metadata, const char* as, int is_data) {
assert(metadata.first == NULL || block_is_const(metadata));
inst* i = inst_new(DEPS);
i->symbol = strdup(name);
jv meta;
if (block_is_const(metadata))
meta = block_const(metadata);
else
meta = jv_object();
if (as)
meta = jv_object_set(meta, jv_string("as"), jv_string(as));
meta = jv_object_set(meta, jv_string("as"), jv_string(as));
meta = jv_object_set(meta, jv_string("is_data"), is_data ? jv_true() : jv_false());
meta = jv_object_set(meta, jv_string("relpath"), jv_string(name));
i->imm.constant = meta;
block_free(metadata);
return inst_block(i);
@@ -797,7 +809,11 @@ block gen_or(block a, block b) {
}
block gen_var_binding(block var, const char* name, block body) {
return BLOCK(gen_op_simple(DUP), var,
// var bindings can be added after coding the program; leave the TOP first.
block top = gen_noop();
if (body.first && body.first->op == TOP)
top = inst_block(block_take(&body));
return BLOCK(top, gen_op_simple(DUP), var,
block_bind(gen_op_unbound(STOREV, name),
body, OP_HAS_VARIABLE));
}
@@ -1084,6 +1100,13 @@ static int compile(struct bytecode* bc, block b) {
code[pos++] = nesting_level(bc, arg->bound_by);
code[pos++] = arg->bound_by->imm.intval | ARG_NEWCLOSURE;
}
} else if ((op->flags & OP_HAS_CONSTANT) && (op->flags & OP_HAS_VARIABLE)) {
// STORE_GLOBAL: constant global, basically
code[pos++] = jv_array_length(jv_copy(constant_pool));
constant_pool = jv_array_append(constant_pool, jv_copy(curr->imm.constant));
code[pos++] = nesting_level(bc, curr->bound_by);
uint16_t var = (uint16_t)curr->bound_by->imm.intval;
code[pos++] = var;
} else if (op->flags & OP_HAS_CONSTANT) {
code[pos++] = jv_array_length(jv_copy(constant_pool));
constant_pool = jv_array_append(constant_pool, jv_copy(curr->imm.constant));
+3 -2
View File
@@ -20,6 +20,7 @@ block gen_noop();
int block_is_noop(block b);
block gen_op_simple(opcode op);
block gen_const(jv constant);
block gen_const_global(jv constant, const char *name);
int block_is_const(block b);
jv_kind block_const_kind(block b);
jv block_const(block b);
@@ -28,9 +29,9 @@ block gen_op_unbound(opcode op, const char* name);
block gen_op_bound(opcode op, block binder);
block gen_op_var_fresh(opcode op, const char* name);
block gen_module(const char* name, block metadata);
block gen_module(block metadata);
jv block_module_meta(block b);
block gen_import(const char* name, block metadata, const char *as);
block gen_import(const char* name, block metadata, const char *as, int is_data);
block gen_function(const char* name, block formals, block body);
block gen_param_regular(const char* name);
block gen_param(const char* name);
+68 -38
View File
@@ -185,8 +185,9 @@ sections:
* `-Ldirectory` / `-L directory`:
Add `directory` to the search list for modules. See the section
on modules below.
Prepend `directory` to the search list for modules. If this
option is used then no builtin search list is used. See the
section on modules below.
* `-e` / `--exit-status`:
@@ -2474,9 +2475,7 @@ sections:
body: |
jq has a library/module system. Modules are files whose names end
in `.jq`. Modules should (but don't have to) start with a
`module` directive. Programs and modules must `import` their
dependencies, if any.
in `.jq`.
Modules imported by a program are searched for in a default search
path (see below). The `import` directive allows the importer to
@@ -2498,29 +2497,20 @@ sections:
the default is appended.
The default search path is the search path given to the `-L`
command-line option, else the "$JQ_LIBRARY_PATH", if set in the
environment, else `["~/.jq", "$ORIGIN/../lib/jq"]` (on Unix) or
`["~/.jq", "$ORIGIN/lib"]` (on Windows).
The `-L` argument's and "$JQ_LIBRARY_PATH" environment variable's
string values are split on ":" on Unix, or ";" on Windows.
command-line option, else `["~/.jq", "$ORIGIN/../lib/jq",
"$ORIGIN/../lib"]`.
Null and empty string path elements terminate search path
processing.
Modules may be organized into a hierarchy, with name components
separated by double-colons ("::"). For example: "foo::bar". Each
component in the name corresponds to a directory on the
filesystem.
A module named "foo::bar" would be searched for in "foo/bar.jq"
and "foo/bar/bar.jq" in the given search path. This is intended to
allow modules to be placed in a directory along with, for example,
version control files, README files, and so on, but also to allow
for single-file modules.
A dependency with relative path "foo/bar" would be searched for in
"foo/bar.jq" and "foo/bar/bar.jq" in the given search path. This
is intended to allow modules to be placed in a directory along
with, for example, version control files, README files, and so on,
but also to allow for single-file modules.
Consecutive components with the same name are not allowed to avoid
ambiguities (e.g., "foo::foo").
ambiguities (e.g., "foo/foo").
For example, with `-L$HOME/.jq` a module `foo` can be found in
`$HOME/.jq/foo.jq` and `$HOME/.jq/foo/foo.jq`.
@@ -2528,38 +2518,78 @@ sections:
If "$HOME/.jq" is a file, it is sourced into the main program.
entries:
- title: "`module NAME {<metadata>};`"
- title: "`import RelativePathString as NAME;`"
body: |
Defines a module named `NAME` with the given metadata. If
given it must be the first thing in the module. Programs also
may use this directive.
Imports a module found at the given path relative to a
directory in a search path. The module's symbols are prefixed
with "NAME::". A ".jq" suffix will be added to the relative
path string.
The metadata must be a constant jq expression. It should be
an object with keys like "version", "repo", "URI", and so on.
At this time jq doesn't use this metadata, but it is made
available to users via the `modulemeta` builtin.
- title: "`import NAME {<metadata>};` `import NAME {<metadata>} as NAME;`"
- title: "`import RelativePathString as NAME {<metadata>};`"
body: |
Imports a module named `NAME`. If the second form is used
then the module's symbols are prefixed with "NAME::".
Imports a module found at the given path relative to a
directory in a search path. The module's symbols are prefixed
with "NAME::". A ".jq" suffix will be added to the relative
path string.
The metadata must be a constant jq expression. It should be
an object with keys like "version", "repo", "URI", and so on.
an object with keys like "homepage" and so on. At this time
jq only uses the "search" key/value of the metadate. The
metadata is also made available to users via the `modulemeta`
builtin.
The "search" key in the metadata, if present, should have a
string or array value (array of strings); this is the search
path to be prefixed to the top-level search path.
- title: "`import RelativePathString as NAME;`"
body: |
Imports a JSON file found at the given path relative to a
directory in a search path. The file's data will be available
as `$NAME::NAME`. A ".json" suffix will be added to the
relative path string.
- title: "`import RelativePathString as $NAME {<metadata>};`"
body: |
Imports a JSON file found at the given path relative to a
directory in a search path. The file's data will be available
as `$NAME::NAME`. A ".json" suffix will be added to the
relative path string.
The metadata must be a constant jq expression. It should be
an object with keys like "homepage" and so on. At this time
jq only uses the "search" key/value of the metadate. The
metadata is also made available to users via the `modulemeta`
builtin.
The "search" key in the metadata, if present, should have a
string or array value (array of strings); this is the search
path to be prefixed to the top-level search path.
- title: "`module {<metadata>};`"
body: |
This directive is entirely optional. It's not required for
proper operation. It serves only the purpose of providing
metadata that can be read with the `modulemeta` builtin.
The metadata must be a constant jq expression. It should be
an object with keys like "homepage". At this time jq doesn't
use this metadata, but it is made available to users via the
`modulemeta` builtin.
- title: "`modulemeta`"
body: |
Takes a module name as input and outputs the module's
metadata, with the module's imports (including metadata) as
the "deps" key.
Takes a module name as input and outputs the module's metadata
as an object, with the module's imports (including metadata)
as an array value for the "deps" key.
Programs can use this to query a module's metadata, which they
could then use to, for example, search for, download, and
install missing dependencies.
+19
View File
@@ -508,6 +508,25 @@ jv jq_next(jq_state *jq) {
break;
}
case STORE_GLOBAL: {
// Get the constant
jv val = jv_array_get(jv_copy(frame_current(jq)->bc->constants), *pc++);
assert(jv_is_valid(val));
// Store the var
uint16_t level = *pc++;
uint16_t v = *pc++;
jv* var = frame_local_var(jq, v, level);
if (jq->debug_trace_enabled) {
printf("V%d = ", v);
jv_dump(jv_copy(val), 0);
printf(" (%d)\n", jv_get_refcnt(val));
}
jv_free(*var);
*var = val;
break;
}
case PATH_BEGIN: {
jv v = stack_pop(jq);
stack_push(jq, jq->path);
+147 -97
View File
@@ -21,33 +21,71 @@ struct lib_loading_state {
block *defs;
uint64_t ct;
};
static int load_library(jq_state *jq, jv lib_path, block *out_block, struct lib_loading_state *lib_state);
static int load_library(jq_state *jq, jv lib_path, int is_data, int raw,
const char *as, block *out_block,
struct lib_loading_state *lib_state);
// Given a lib_path to search first, creates a chain of search paths
// in the following order:
// 1. lib_path
// 2. -L paths passed in on the command line (from jq_state*)
// 3. JQ_LIBRARY_PATH environment variable
jv build_lib_search_chain(jq_state *jq, jv search_path) {
// 2. -L paths passed in on the command line (from jq_state*) or builtin list
static jv build_lib_search_chain(jq_state *jq, jv search_path, jv jq_origin, jv lib_origin) {
assert(jv_get_kind(search_path) == JV_KIND_ARRAY);
jv expanded = jv_array();
jv expanded_elt;
jv_array_foreach(search_path, i, path) {
path = expand_path(path);
if (jv_is_valid(path))
expanded = jv_array_append(expanded, expand_path(path));
else
jv_free(path);
if (strcmp(".",jv_string_value(path)) == 0) {
expanded_elt = jv_copy(path);
} else if (jv_get_kind(lib_origin) == JV_KIND_STRING &&
strncmp("./", jv_string_value(path),sizeof("./")-1) == 0) {
expanded_elt = jv_string_fmt("%s/%s",
jv_string_value(lib_origin),
jv_string_value(path) + sizeof ("./") - 1);
} else if (strncmp("$ORIGIN/",jv_string_value(path),sizeof("$ORIGIN/")-1) == 0) {
expanded_elt = jv_string_fmt("%s/%s",
jv_string_value(jq_origin),
jv_string_value(path) + sizeof ("$ORIGIN/") - 1);
} else {
expanded_elt = expand_path(path);
if (!jv_is_valid(expanded_elt)) {
jv_free(search_path);
jv_free(expanded);
jv_free(path);
return expanded_elt;
}
path = jv_invalid();
}
expanded = jv_array_append(expanded, expanded_elt);
jv_free(path);
}
jv_free(jq_origin);
jv_free(lib_origin);
jv_free(search_path);
expanded = jv_array_concat(expanded, jq_get_lib_dirs(jq));
return expanded;
}
static jv name2relpath(jv name) {
jv components = jv_string_split(jv_copy(name), jv_string("::"));
// Doesn't actually check that name not be an absolute path; we could
// just always prepend "./"!
static jv validate_relpath(jv name) {
const char *s = jv_string_value(name);
if (strchr(s, '\\')) {
jv res = jv_invalid_with_msg(jv_string_fmt("Modules must be named by relative paths using '/', not '\\' (%s)", s));
jv_free(name);
return res;
}
jv components = jv_string_split(jv_copy(name), jv_string("/"));
jv rp = jv_array_get(jv_copy(components), 0);
components = jv_array_slice(components, 1, jv_array_length(jv_copy(components)));
jv_array_foreach(components, i, x) {
if (!strcmp(jv_string_value(x), "..")) {
jv_free(x);
jv_free(rp);
jv_free(components);
jv res = jv_invalid_with_msg(jv_string_fmt("Relative paths to modules may not traverse to parent directories (%s)", s));
jv_free(name);
return res;
}
if (i > 0 && jv_equal(jv_copy(x), jv_array_get(jv_copy(components), i - 1))) {
jv_free(x);
jv_free(rp);
@@ -64,22 +102,33 @@ static jv name2relpath(jv name) {
return rp;
}
static jv find_lib(jq_state *jq, jv lib_name, jv lib_search_path) {
assert(jv_get_kind(lib_search_path) == JV_KIND_ARRAY);
assert(jv_get_kind(lib_name) == JV_KIND_STRING);
// Assumes name has been validated
static jv jv_basename(jv name) {
const char *s = jv_string_value(name);
const char *p = strrchr(s, '/');
if (!p)
return name;
jv res = jv_string_fmt("%s", p);
jv_free(name);
return res;
}
jv rel_path = name2relpath(jv_copy(lib_name));
if (!jv_is_valid(rel_path)) {
jv_free(lib_name);
return rel_path;
}
// Asummes validated relative path to module
static jv find_lib(jq_state *jq, jv rel_path, jv search, const char *suffix, jv jq_origin, jv lib_origin) {
if (jv_get_kind(search) != JV_KIND_ARRAY)
return jv_invalid_with_msg(jv_string_fmt("Module search path must be an array"));
if (jv_get_kind(rel_path) != JV_KIND_STRING)
return jv_invalid_with_msg(jv_string_fmt("Module path must be a string"));
struct stat st;
int ret;
jv lib_search_paths = build_lib_search_chain(jq, lib_search_path);
// Ideally we should cache this somewhere
search = build_lib_search_chain(jq, search, jq_origin, lib_origin);
jv_array_foreach(lib_search_paths, i, spath) {
jv bname = jv_basename(jv_copy(rel_path));
jv_array_foreach(search, i, spath) {
if (jv_get_kind(spath) == JV_KIND_NULL) {
jv_free(spath);
break;
@@ -89,43 +138,44 @@ static jv find_lib(jq_state *jq, jv lib_name, jv lib_search_path) {
jv_free(spath);
continue; /* XXX report non-strings in search path?? */
}
jv testpath = jq_realpath(jv_string_fmt("%s/%s.jq",
// Try .../module/last/component.jq
jv testpath = jq_realpath(jv_string_fmt("%s/%s%s",
jv_string_value(spath),
jv_string_value(rel_path)));
jv_string_value(rel_path),
suffix));
ret = stat(jv_string_value(testpath),&st);
if (ret == -1 && errno == ENOENT) {
jv_free(testpath);
testpath = jq_realpath(jv_string_fmt("%s/%s/%s.jq",
// Try .../module/last/component/component.jq
testpath = jq_realpath(jv_string_fmt("%s/%s/%s%s",
jv_string_value(spath),
jv_string_value(rel_path),
jv_string_value(lib_name)));
jv_string_value(bname),
suffix));
ret = stat(jv_string_value(testpath),&st);
}
if (ret == 0) {
jv_free(spath);
jv_free(rel_path);
jv_free(lib_name);
jv_free(lib_search_paths);
jv_free(search);
jv_free(bname);
jv_free(spath);
return testpath;
}
jv_free(testpath);
jv_free(spath);
}
jv output = jv_invalid_with_msg(jv_string_fmt("module not found: %s", jv_string_value(lib_name)));
jv output = jv_invalid_with_msg(jv_string_fmt("module not found: %s", jv_string_value(rel_path)));
jv_free(rel_path);
jv_free(lib_name);
jv_free(lib_search_paths);
jv_free(search);
jv_free(bname);
return output;
}
static int version_matches(jq_state *jq, block importer, block module) {
return 1;
}
static jv default_search(jv value) {
static jv default_search(jq_state *jq, jv value) {
if (!jv_is_valid(value)) {
// dependent didn't say; prepend . to system search path listj
jv_free(value);
return JV_ARRAY(jv_string("."), jv_string("$ORIGIN"));
return jv_array_concat(JV_ARRAY(jv_string(".")), jq_get_lib_dirs(jq));
}
if (jv_get_kind(value) != JV_KIND_ARRAY)
return JV_ARRAY(value);
@@ -139,38 +189,23 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
int nerrors = 0;
jv_array_foreach(deps, i, dep) {
jv name = jv_object_get(jv_copy(dep), jv_string("name"));
int is_data = jv_get_kind(jv_object_get(jv_copy(dep), jv_string("is_data"))) == JV_KIND_TRUE;
int raw = 0;
jv v = jv_object_get(jv_copy(dep), jv_string("raw"));
if (jv_get_kind(v) == JV_KIND_TRUE)
raw = 1;
jv_free(v);
jv relpath = validate_relpath(jv_object_get(jv_copy(dep), jv_string("relpath")));
jv as = jv_object_get(jv_copy(dep), jv_string("as"));
if (!jv_is_valid(as)) {
jv_free(as);
as = jv_string("");
}
jv search = default_search(jv_object_get(dep, jv_string("search")));
jv_array_foreach(search, k, search_elt) {
if (strcmp(".",jv_string_value(search_elt)) == 0) {
jv tsearch = jv_copy(lib_origin);
jv_free(search_elt);
search = jv_array_set(search, k, tsearch);
} else if (strncmp("./",jv_string_value(search_elt),sizeof("./")-1) == 0) {
jv tsearch = jv_string_fmt("%s/%s",
jv_string_value(lib_origin),
jv_string_value(search_elt) + sizeof ("./") - 1);
jv_free(search_elt);
search = jv_array_set(search, k, tsearch);
} else if (strncmp("$ORIGIN/",jv_string_value(search_elt),sizeof("$ORIGIN/")-1) == 0) {
jv tsearch = jv_string_fmt("%s/%s",
jv_string_value(jq_origin),
jv_string_value(search_elt) + sizeof ("$ORIGIN/") - 1);
jv_free(search_elt);
search = jv_array_set(search, k, tsearch);
} else {
jv_free(search_elt);
}
}
jv lib_path = find_lib(jq, name, search);
assert(jv_is_valid(as) && jv_get_kind(as) == JV_KIND_STRING);
jv search = default_search(jq, jv_object_get(dep, jv_string("search")));
// dep is now freed; do not reuse
// find_lib does a lot of work that could be cached...
jv resolved = find_lib(jq, relpath, search, is_data ? ".json" : ".jq", jv_copy(jq_origin), jv_copy(lib_origin));
// XXX ...move the rest of this into a callback.
if (!jv_is_valid(lib_path)) {
jv emsg = jv_invalid_get_msg(lib_path);
if (!jv_is_valid(resolved)) {
jv emsg = jv_invalid_get_msg(resolved);
jq_report_error(jq, jv_string_fmt("jq: error: %s\n",jv_string_value(emsg)));
jv_free(emsg);
jv_free(as);
@@ -181,26 +216,20 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
}
uint64_t state_idx = 0;
for (; state_idx < lib_state->ct; ++state_idx) {
if (strcmp(lib_state->names[state_idx],jv_string_value(lib_path)) == 0)
if (strcmp(lib_state->names[state_idx],jv_string_value(resolved)) == 0)
break;
}
if (state_idx < lib_state->ct) { // Found
// XXX Check version matching here!
if (version_matches(jq, bk, lib_state->defs[state_idx]))
bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
else
// XXX Would be nice to have the dependent's name here too
jq_report_error(jq, jv_string_fmt("jq: error: version mismatch for %s", jv_string_value(name)));
jv_free(lib_path);
jv_free(resolved);
// Bind the library to the program
bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
} else { // Not found. Add it to the table before binding.
block dep_def_block = gen_noop();
nerrors += load_library(jq, lib_path, &dep_def_block, lib_state);
nerrors += load_library(jq, resolved, is_data, raw, jv_string_value(as), &dep_def_block, lib_state);
// resolved has been freed
if (nerrors == 0) {
// XXX Check version matching here!
if (version_matches(jq, bk, dep_def_block))
bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
else
jq_report_error(jq, jv_string_fmt("jq: error: version mismatch for %s", jv_string_value(name)));
// Bind the library to the program
bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
}
}
jv_free(as);
@@ -213,30 +242,48 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
// Loads the library at lib_path into lib_state, putting the library's defs
// into *out_block
static int load_library(jq_state *jq, jv lib_path, block *out_block, struct lib_loading_state *lib_state) {
static int load_library(jq_state *jq, jv lib_path, int is_data, int raw, const char *as, block *out_block, struct lib_loading_state *lib_state) {
int nerrors = 0;
struct locfile* src;
struct locfile* src = NULL;
block program;
jv data = jv_load_file(jv_string_value(lib_path), 1);
jv data;
if (is_data && !raw)
data = jv_load_file(jv_string_value(lib_path), 0);
else
data = jv_load_file(jv_string_value(lib_path), 1);
int state_idx;
if (jv_is_valid(data)) {
if (!jv_is_valid(data)) {
if (jv_invalid_has_msg(jv_copy(data)))
data = jv_invalid_get_msg(data);
else
data = jv_string("unknown error");
jq_report_error(jq, jv_string_fmt("jq: error loading data file %s: %s\n", jv_string_value(lib_path), jv_string_value(data)));
nerrors++;
goto out;
} else if (is_data) {
// import "foo" as $bar;
program = gen_const_global(jv_copy(data), as);
} else {
// import "foo" as bar;
src = locfile_init(jq, jv_string_value(data), jv_string_length_bytes(jv_copy(data)));
nerrors += jq_parse_library(src, &program);
if (nerrors == 0) {
state_idx = lib_state->ct++;
lib_state->names = realloc(lib_state->names, lib_state->ct * sizeof(const char *));
lib_state->defs = realloc(lib_state->defs, lib_state->ct * sizeof(block));
lib_state->names[state_idx] = strdup(jv_string_value(lib_path));
lib_state->defs[state_idx] = program;
char *lib_origin = strdup(jv_string_value(lib_path));
nerrors += process_dependencies(jq, jq_get_jq_origin(jq),
jv_string(dirname(lib_origin)),
&lib_state->defs[state_idx], lib_state);
&program, lib_state);
free(lib_origin);
*out_block = lib_state->defs[state_idx];
}
locfile_free(src);
}
state_idx = lib_state->ct++;
lib_state->names = realloc(lib_state->names, lib_state->ct * sizeof(const char *));
lib_state->defs = realloc(lib_state->defs, lib_state->ct * sizeof(block));
lib_state->names[state_idx] = strdup(jv_string_value(lib_path));
lib_state->defs[state_idx] = program;
*out_block = program;
if (src)
locfile_free(src);
out:
jv_free(lib_path);
jv_free(data);
return nerrors;
@@ -244,8 +291,11 @@ static int load_library(jq_state *jq, jv lib_path, block *out_block, struct lib_
// FIXME It'd be nice to have an option to search the same search path
// as we do in process_dependencies.
jv load_module_meta(jq_state *jq, jv modname) {
jv lib_path = find_lib(jq, modname, jv_array());
jv load_module_meta(jq_state *jq, jv mod_relpath) {
// We can't know the caller's origin; we could though, if it was passed in
jv lib_path = find_lib(jq, validate_relpath(mod_relpath), jq_get_lib_dirs(jq), ".jq", jq_get_jq_origin(jq), jv_null());
if (!jv_is_valid(lib_path))
return lib_path;
jv meta = jv_null();
jv data = jv_load_file(jv_string_value(lib_path), 1);
if (jv_is_valid(data)) {
@@ -278,7 +328,7 @@ int load_program(jq_state *jq, struct locfile* src, block *out_block) {
block libs = gen_noop();
for (uint64_t i = 0; i < lib_state.ct; ++i) {
free(lib_state.names[i]);
if (nerrors == 0)
if (nerrors == 0 && !block_is_const(lib_state.defs[i]))
libs = block_join(libs, lib_state.defs[i]);
else
block_free(lib_state.defs[i]);
+5 -17
View File
@@ -444,23 +444,11 @@ int main(int argc, char* argv[]) {
if (options & COLOUR_OUTPUT) dumpopts |= JV_PRINT_COLOUR;
if (options & NO_COLOUR_OUTPUT) dumpopts &= ~JV_PRINT_COLOUR;
char *penv = getenv("JQ_LIBRARY_PATH");
if (penv && jv_get_kind(lib_search_paths) == JV_KIND_NULL) {
// Use $JQ_LIBRARY_PATH
#ifdef WIN32
#define PATH_ENV_SEPARATOR ";"
#else
#define PATH_ENV_SEPARATOR ":"
#endif
lib_search_paths = jv_array_concat(lib_search_paths,jv_string_split(jv_string(penv),jv_string(PATH_ENV_SEPARATOR)));
#undef PATH_ENV_SEPARATOR
} else if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) {
// Use compiled-in default JQ_LIBRARY_PATH
#ifdef WIN32
lib_search_paths = JV_ARRAY(jv_string("~/.jq"), jv_string("$ORIGIN/lib"));
#else
lib_search_paths = JV_ARRAY(jv_string("~/.jq"), jv_string("$ORIGIN/../lib/jq"));
#endif
if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) {
// Default search path list
lib_search_paths = JV_ARRAY(jv_string("~/.jq"),
jv_string("$ORIGIN/../lib/jq"),
jv_string("$ORIGIN/lib"));
}
jq_set_attr(jq, jv_string("JQ_LIBRARY_PATH"), lib_search_paths);
+1
View File
@@ -6,6 +6,7 @@ OP(POP, NONE, 1, 0)
OP(LOADV, VARIABLE, 1, 1)
OP(LOADVN, VARIABLE, 1, 1)
OP(STOREV, VARIABLE, 1, 0)
OP(STORE_GLOBAL, GLOBAL, 0, 0)
OP(INDEX, NONE, 2, 1)
OP(INDEX_OPT, NONE, 2, 1)
OP(EACH, NONE, 1, 1)
+33 -22
View File
@@ -280,14 +280,13 @@ Module:
%empty {
$$ = gen_noop();
} |
"module" IDENT Exp ';' {
if (!block_is_const($3)) {
"module" Exp ';' {
if (!block_is_const($2)) {
FAIL(@$, "Module metadata must be constant.");
$$ = gen_noop();
} else {
$$ = gen_module(jv_string_value($2), $3);
$$ = gen_module($2);
}
jv_free($2);
}
Imports:
@@ -479,33 +478,45 @@ Term {
}
Import:
"import" IDENT ';' {
$$ = gen_import(jv_string_value($2), gen_noop(), NULL);
jv_free($2);
"import" String "as" '$' IDENT ';' {
jv v = block_const($2);
// XXX Make gen_import take only blocks and the int is_data so we
// don't have to free so much stuff here
$$ = gen_import(jv_string_value(v), gen_noop(), jv_string_value($5), 1);
block_free($2);
jv_free($5);
jv_free(v);
} |
"import" IDENT Exp ';' {
if (!block_is_const($3)) {
FAIL(@$, "Module metadata must be constant.");
$$ = gen_noop();
} else {
$$ = gen_import(jv_string_value($2), $3, NULL);
}
jv_free($2);
} |
"import" IDENT "as" IDENT ';' {
$$ = gen_import(jv_string_value($2), gen_noop(), jv_string_value($4));
jv_free($2);
"import" String "as" IDENT ';' {
jv v = block_const($2);
$$ = gen_import(jv_string_value(v), gen_noop(), jv_string_value($4), 0);
block_free($2);
jv_free($4);
jv_free(v);
} |
"import" IDENT "as" IDENT Exp ';' {
"import" String "as" IDENT Exp ';' {
if (!block_is_const($5)) {
FAIL(@$, "Module metadata must be constant.");
$$ = gen_noop();
} else {
$$ = gen_import(jv_string_value($2), $5, jv_string_value($4));
jv v = block_const($2);
$$ = gen_import(jv_string_value(v), $5, jv_string_value($4), 0);
jv_free(v);
}
jv_free($2);
block_free($2);
jv_free($4);
} |
"import" String "as" '$' IDENT Exp ';' {
if (!block_is_const($6)) {
FAIL(@$, "Module metadata must be constant.");
$$ = gen_noop();
} else {
jv v = block_const($2);
$$ = gen_import(jv_string_value(v), $6, jv_string_value($5), 1);
jv_free(v);
}
block_free($2);
jv_free($5);
}
FuncDef:
+1 -1
View File
@@ -1,2 +1,2 @@
module a {version:1.7};
module {version:1.7};
def a: "a";
+9 -7
View File
@@ -1,12 +1,14 @@
module c {whatever:null};
import a as foo;
import d as d {search:"./"};
import d {search:"./"};
import e as e {search:"./../lib/jq"};
import f as f {search:"./../lib/jq"};
module {whatever:null};
import "a" as foo;
import "d" as d {search:"./"};
import "d" as d2{search:"./"};
import "e" as e {search:"./../lib/jq"};
import "f" as f {search:"./../lib/jq"};
import "data" as $d;
def a: 0;
def c:
if meh != d::meh then error("import into namespace doesn't work")
if $d::d[0] != {this:"is a test",that:"is too"} then error("data import is busted")
elif d2::meh != d::meh then error("import twice doesn't work")
elif foo::a != "a" then error("foo::a didn't work as expected")
elif d::meh != "meh" then error("d::meh didn't work as expected")
elif e::bah != "bah" then error("e::bah didn't work as expected")
+5 -5
View File
@@ -159,7 +159,7 @@ if which seq > /dev/null 2>&1; then
fi
dd "if=tests/torture/input0.json" bs=$i count=1 2>/dev/null |
$VALGRIND ./jq -cn --stream -L "$mods" 'import streaming; tovalues(inputs)' > $d/out1 2>$d/err || true
$VALGRIND ./jq -cn --stream -L "$mods" 'import "streaming" as streaming; streaming::tovalues(inputs)' > $d/out1 2>$d/err || true
if [ -n "$VALGRIND" ]; then
grep '^==[0-9][0-9]*== ERROR SUMMARY: 0 errors' $d/err > /dev/null
else
@@ -200,22 +200,22 @@ if [ `HOME="$mods" $VALGRIND $Q ./jq --debug-dump-disasm -n fg | grep '^[a-z]' |
exit 1
fi
if ! $VALGRIND $Q ./jq -ner -L "$mods" 'import a as foo; import b as bar; import a; def fooa: foo::a; [fooa, bar::a, bar::b, foo::a, a] | . == ["a","b","c","a","a"]' > /dev/null; then
if ! $VALGRIND $Q ./jq -ner -L "$mods" 'import "a" as foo; import "b" as bar; def fooa: foo::a; [fooa, bar::a, bar::b, foo::a] | . == ["a","b","c","a"]' > /dev/null; then
echo "Module system appears to be broken" 1>&2
exit 1
fi
if ! $VALGRIND $Q ./jq -ner -L "$mods" 'import c as foo; [foo::a, foo::c] | . == [0,"acmehbah"]' > /dev/null; then
if ! $VALGRIND $Q ./jq -ner -L "$mods" 'import "c" as foo; [foo::a, foo::c] | . == [0,"acmehbah"]' > /dev/null; then
echo "Module system appears to be broken" 1>&2
exit 1
fi
if [ "`$VALGRIND $Q ./jq -cner -L "$mods" '\"c\" | modulemeta'`" != '{"whatever":null,"name":"c","deps":[{"as":"foo","name":"a"},{"search":"./","as":"d","name":"d"},{"search":"./","name":"d"},{"search":"./../lib/jq","as":"e","name":"e"},{"search":"./../lib/jq","as":"f","name":"f"}]}' ]; then
if [ "`$VALGRIND $Q ./jq -cner -L "$mods" '\"c\" | modulemeta'`" != '{"whatever":null,"deps":[{"as":"foo","is_data":false,"relpath":"a"},{"search":"./","as":"d","is_data":false,"relpath":"d"},{"search":"./","as":"d2","is_data":false,"relpath":"d"},{"search":"./../lib/jq","as":"e","is_data":false,"relpath":"e"},{"search":"./../lib/jq","as":"f","is_data":false,"relpath":"f"},{"as":"d","is_data":true,"relpath":"data"}]}' ]; then
echo "modulemeta builtin appears to be broken" 1>&2
exit 1
fi
if $VALGRIND ./jq -ner -L "$mods" 'import syntaxerror; .' > $d/out 2>&1; then
if $VALGRIND ./jq -ner -L "$mods" 'import "syntaxerror" as e; .' > $d/out 2>&1; then
echo "Module system appears to be broken" 1>&2
exit 1
fi