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; 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) { 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) { if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) {
jv_free(a); jv_free(a);
@@ -937,6 +952,9 @@ static const struct cfunction function_list[] = {
{(cfunction_ptr)f_error, "error", 2}, {(cfunction_ptr)f_error, "error", 2},
{(cfunction_ptr)f_format, "format", 2}, {(cfunction_ptr)f_format, "format", 2},
{(cfunction_ptr)f_env, "env", 1}, {(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_match, "_match_impl", 4},
{(cfunction_ptr)f_modulemeta, "modulemeta", 1}, {(cfunction_ptr)f_modulemeta, "modulemeta", 1},
{(cfunction_ptr)f_input, "_input", 1}, {(cfunction_ptr)f_input, "_input", 1},
+1
View File
@@ -9,6 +9,7 @@
#define NONE 0, 1 #define NONE 0, 1
#define CONSTANT OP_HAS_CONSTANT, 2 #define CONSTANT OP_HAS_CONSTANT, 2
#define VARIABLE (OP_HAS_VARIABLE | OP_HAS_BINDING), 3 #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 BRANCH OP_HAS_BRANCH, 2
#define CFUNC (OP_HAS_CFUNC | OP_HAS_BINDING), 3 #define CFUNC (OP_HAS_CFUNC | OP_HAS_BINDING), 3
#define UFUNC (OP_HAS_UFUNC | OP_HAS_BINDING | OP_IS_CALL_PSEUDO), 4 #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); 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) { int block_is_const(block b) {
return (block_is_single(b) && b.first->op == LOADK); 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; 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) { int block_has_only_binders(block binders, int bindflags) {
bindflags |= OP_HAS_BINDING; bindflags |= OP_HAS_BINDING;
bindflags &= ~OP_BIND_WILDCARD; 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] == '*' && ((bindflags & OP_BIND_WILDCARD) && i->symbol[0] == '*' &&
break_distance <= 3 && (i->symbol[1] == '1' + break_distance) && break_distance <= 3 && (i->symbol[1] == '1' + break_distance) &&
i->symbol[2] == '\0'))) { i->symbol[2] == '\0'))) {
if (bindflags & OP_BIND_WILDCARD) {
jv_mem_free(i->symbol);
i->symbol = strdup(binder.first->symbol);
}
// bind this instruction // bind this instruction
if (i->op == CALL_JQ && i->nactuals == -1) if (i->op == CALL_JQ && i->nactuals == -1)
i->nactuals = block_count_actuals(i->arglist); 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) { block block_bind_library(block binder, block body, int bindflags, const char* libname) {
assert(block_has_only_binders(binder, bindflags));
bindflags |= OP_HAS_BINDING; bindflags |= OP_HAS_BINDING;
int nrefs = 0; int nrefs = 0;
int matchlen = strlen(libname); int matchlen = strlen(libname);
@@ -363,13 +371,21 @@ block block_bind_library(block binder, block body, int bindflags, const char* li
strcpy(matchname+matchlen,"::"); strcpy(matchname+matchlen,"::");
matchlen += 2; matchlen += 2;
} }
assert(block_has_only_binders(binder, bindflags));
for (inst *curr = binder.first; curr; curr = curr->next) { for (inst *curr = binder.first; curr; curr = curr->next) {
int bindflags2 = bindflags;
char* cname = curr->symbol; char* cname = curr->symbol;
char* tname = malloc(strlen(curr->symbol)+matchlen+1); char* tname = malloc(strlen(curr->symbol)+matchlen+1);
strcpy(tname, matchname); strcpy(tname, matchname);
strcpy(tname+matchlen,cname); 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; 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; curr->symbol = cname;
free(tname); free(tname);
} }
@@ -440,15 +456,13 @@ jv block_take_imports(block* body) {
jv imports = jv_array(); jv imports = jv_array();
inst* top = NULL; inst* top = NULL;
if (body->first->op == TOP) { if (body->first && body->first->op == TOP) {
top = block_take(body); top = block_take(body);
} }
while (body->first && (body->first->op == MODULEMETA || body->first->op == DEPS)) { while (body->first && (body->first->op == MODULEMETA || body->first->op == DEPS)) {
inst* dep = block_take(body); inst* dep = block_take(body);
if (dep->op == DEPS) { if (dep->op == DEPS) {
jv opts = jv_copy(dep->imm.constant); imports = jv_array_append(imports, jv_copy(dep->imm.constant));
opts = jv_object_set(opts,jv_string("name"),jv_string(dep->symbol));
imports = jv_array_append(imports, opts);
} }
inst_free(dep); inst_free(dep);
} }
@@ -458,13 +472,11 @@ jv block_take_imports(block* body) {
return imports; return imports;
} }
block gen_module(const char* name, block metadata) { block gen_module(block metadata) {
inst* i = inst_new(MODULEMETA); inst* i = inst_new(MODULEMETA);
i->symbol = strdup(name);
i->imm.constant = block_const(metadata); i->imm.constant = block_const(metadata);
if (jv_get_kind(i->imm.constant) != JV_KIND_OBJECT) 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(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); block_free(metadata);
return inst_block(i); return inst_block(i);
} }
@@ -475,17 +487,17 @@ jv block_module_meta(block b) {
return jv_null(); 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)); assert(metadata.first == NULL || block_is_const(metadata));
inst* i = inst_new(DEPS); inst* i = inst_new(DEPS);
i->symbol = strdup(name);
jv meta; jv meta;
if (block_is_const(metadata)) if (block_is_const(metadata))
meta = block_const(metadata); meta = block_const(metadata);
else else
meta = jv_object(); 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; i->imm.constant = meta;
block_free(metadata); block_free(metadata);
return inst_block(i); 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) { 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), block_bind(gen_op_unbound(STOREV, name),
body, OP_HAS_VARIABLE)); 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++] = nesting_level(bc, arg->bound_by);
code[pos++] = arg->bound_by->imm.intval | ARG_NEWCLOSURE; 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) { } else if (op->flags & OP_HAS_CONSTANT) {
code[pos++] = jv_array_length(jv_copy(constant_pool)); code[pos++] = jv_array_length(jv_copy(constant_pool));
constant_pool = jv_array_append(constant_pool, jv_copy(curr->imm.constant)); 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); int block_is_noop(block b);
block gen_op_simple(opcode op); block gen_op_simple(opcode op);
block gen_const(jv constant); block gen_const(jv constant);
block gen_const_global(jv constant, const char *name);
int block_is_const(block b); int block_is_const(block b);
jv_kind block_const_kind(block b); jv_kind block_const_kind(block b);
jv block_const(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_bound(opcode op, block binder);
block gen_op_var_fresh(opcode op, const char* name); 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); 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_function(const char* name, block formals, block body);
block gen_param_regular(const char* name); block gen_param_regular(const char* name);
block gen_param(const char* name); block gen_param(const char* name);
+68 -38
View File
@@ -185,8 +185,9 @@ sections:
* `-Ldirectory` / `-L directory`: * `-Ldirectory` / `-L directory`:
Add `directory` to the search list for modules. See the section Prepend `directory` to the search list for modules. If this
on modules below. option is used then no builtin search list is used. See the
section on modules below.
* `-e` / `--exit-status`: * `-e` / `--exit-status`:
@@ -2474,9 +2475,7 @@ sections:
body: | body: |
jq has a library/module system. Modules are files whose names end jq has a library/module system. Modules are files whose names end
in `.jq`. Modules should (but don't have to) start with a in `.jq`.
`module` directive. Programs and modules must `import` their
dependencies, if any.
Modules imported by a program are searched for in a default search Modules imported by a program are searched for in a default search
path (see below). The `import` directive allows the importer to path (see below). The `import` directive allows the importer to
@@ -2498,29 +2497,20 @@ sections:
the default is appended. the default is appended.
The default search path is the search path given to the `-L` The default search path is the search path given to the `-L`
command-line option, else the "$JQ_LIBRARY_PATH", if set in the command-line option, else `["~/.jq", "$ORIGIN/../lib/jq",
environment, else `["~/.jq", "$ORIGIN/../lib/jq"]` (on Unix) or "$ORIGIN/../lib"]`.
`["~/.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.
Null and empty string path elements terminate search path Null and empty string path elements terminate search path
processing. processing.
Modules may be organized into a hierarchy, with name components A dependency with relative path "foo/bar" would be searched for in
separated by double-colons ("::"). For example: "foo::bar". Each "foo/bar.jq" and "foo/bar/bar.jq" in the given search path. This
component in the name corresponds to a directory on the is intended to allow modules to be placed in a directory along
filesystem. with, for example, version control files, README files, and so on,
but also to allow for single-file modules.
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.
Consecutive components with the same name are not allowed to avoid 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 For example, with `-L$HOME/.jq` a module `foo` can be found in
`$HOME/.jq/foo.jq` and `$HOME/.jq/foo/foo.jq`. `$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. If "$HOME/.jq" is a file, it is sourced into the main program.
entries: entries:
- title: "`module NAME {<metadata>};`" - title: "`import RelativePathString as NAME;`"
body: | body: |
Defines a module named `NAME` with the given metadata. If Imports a module found at the given path relative to a
given it must be the first thing in the module. Programs also directory in a search path. The module's symbols are prefixed
may use this directive. with "NAME::". A ".jq" suffix will be added to the relative
path string.
The metadata must be a constant jq expression. It should be - title: "`import RelativePathString as NAME {<metadata>};`"
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;`"
body: | body: |
Imports a module named `NAME`. If the second form is used Imports a module found at the given path relative to a
then the module's symbols are prefixed with "NAME::". 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 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 The "search" key in the metadata, if present, should have a
string or array value (array of strings); this is the search string or array value (array of strings); this is the search
path to be prefixed to the top-level search path. 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`" - title: "`modulemeta`"
body: | body: |
Takes a module name as input and outputs the module's Takes a module name as input and outputs the module's metadata
metadata, with the module's imports (including metadata) as as an object, with the module's imports (including metadata)
the "deps" key. as an array value for the "deps" key.
Programs can use this to query a module's metadata, which they Programs can use this to query a module's metadata, which they
could then use to, for example, search for, download, and could then use to, for example, search for, download, and
install missing dependencies. install missing dependencies.
+19
View File
@@ -508,6 +508,25 @@ jv jq_next(jq_state *jq) {
break; 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: { case PATH_BEGIN: {
jv v = stack_pop(jq); jv v = stack_pop(jq);
stack_push(jq, jq->path); stack_push(jq, jq->path);
+147 -97
View File
@@ -21,33 +21,71 @@ struct lib_loading_state {
block *defs; block *defs;
uint64_t ct; 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 // Given a lib_path to search first, creates a chain of search paths
// in the following order: // in the following order:
// 1. lib_path // 1. lib_path
// 2. -L paths passed in on the command line (from jq_state*) // 2. -L paths passed in on the command line (from jq_state*) or builtin list
// 3. JQ_LIBRARY_PATH environment variable static jv build_lib_search_chain(jq_state *jq, jv search_path, jv jq_origin, jv lib_origin) {
jv build_lib_search_chain(jq_state *jq, jv search_path) {
assert(jv_get_kind(search_path) == JV_KIND_ARRAY); assert(jv_get_kind(search_path) == JV_KIND_ARRAY);
jv expanded = jv_array(); jv expanded = jv_array();
jv expanded_elt;
jv_array_foreach(search_path, i, path) { jv_array_foreach(search_path, i, path) {
path = expand_path(path); if (strcmp(".",jv_string_value(path)) == 0) {
if (jv_is_valid(path)) expanded_elt = jv_copy(path);
expanded = jv_array_append(expanded, expand_path(path)); } else if (jv_get_kind(lib_origin) == JV_KIND_STRING &&
else strncmp("./", jv_string_value(path),sizeof("./")-1) == 0) {
jv_free(path); 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); jv_free(search_path);
expanded = jv_array_concat(expanded, jq_get_lib_dirs(jq));
return expanded; return expanded;
} }
static jv name2relpath(jv name) { // Doesn't actually check that name not be an absolute path; we could
jv components = jv_string_split(jv_copy(name), jv_string("::")); // 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); jv rp = jv_array_get(jv_copy(components), 0);
components = jv_array_slice(components, 1, jv_array_length(jv_copy(components))); components = jv_array_slice(components, 1, jv_array_length(jv_copy(components)));
jv_array_foreach(components, i, x) { 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))) { if (i > 0 && jv_equal(jv_copy(x), jv_array_get(jv_copy(components), i - 1))) {
jv_free(x); jv_free(x);
jv_free(rp); jv_free(rp);
@@ -64,22 +102,33 @@ static jv name2relpath(jv name) {
return rp; return rp;
} }
static jv find_lib(jq_state *jq, jv lib_name, jv lib_search_path) { // Assumes name has been validated
assert(jv_get_kind(lib_search_path) == JV_KIND_ARRAY); static jv jv_basename(jv name) {
assert(jv_get_kind(lib_name) == JV_KIND_STRING); 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)); // Asummes validated relative path to module
if (!jv_is_valid(rel_path)) { static jv find_lib(jq_state *jq, jv rel_path, jv search, const char *suffix, jv jq_origin, jv lib_origin) {
jv_free(lib_name); if (jv_get_kind(search) != JV_KIND_ARRAY)
return rel_path; 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; struct stat st;
int ret; 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) { if (jv_get_kind(spath) == JV_KIND_NULL) {
jv_free(spath); jv_free(spath);
break; break;
@@ -89,43 +138,44 @@ static jv find_lib(jq_state *jq, jv lib_name, jv lib_search_path) {
jv_free(spath); jv_free(spath);
continue; /* XXX report non-strings in search path?? */ 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(spath),
jv_string_value(rel_path))); jv_string_value(rel_path),
suffix));
ret = stat(jv_string_value(testpath),&st); ret = stat(jv_string_value(testpath),&st);
if (ret == -1 && errno == ENOENT) { if (ret == -1 && errno == ENOENT) {
jv_free(testpath); 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(spath),
jv_string_value(rel_path), jv_string_value(rel_path),
jv_string_value(lib_name))); jv_string_value(bname),
suffix));
ret = stat(jv_string_value(testpath),&st); ret = stat(jv_string_value(testpath),&st);
} }
if (ret == 0) { if (ret == 0) {
jv_free(spath);
jv_free(rel_path); jv_free(rel_path);
jv_free(lib_name); jv_free(search);
jv_free(lib_search_paths); jv_free(bname);
jv_free(spath);
return testpath; return testpath;
} }
jv_free(testpath); jv_free(testpath);
jv_free(spath); 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(rel_path);
jv_free(lib_name); jv_free(search);
jv_free(lib_search_paths); jv_free(bname);
return output; return output;
} }
static int version_matches(jq_state *jq, block importer, block module) { static jv default_search(jq_state *jq, jv value) {
return 1;
}
static jv default_search(jv value) {
if (!jv_is_valid(value)) { if (!jv_is_valid(value)) {
// dependent didn't say; prepend . to system search path listj
jv_free(value); 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) if (jv_get_kind(value) != JV_KIND_ARRAY)
return JV_ARRAY(value); 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; int nerrors = 0;
jv_array_foreach(deps, i, dep) { 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")); jv as = jv_object_get(jv_copy(dep), jv_string("as"));
if (!jv_is_valid(as)) { assert(jv_is_valid(as) && jv_get_kind(as) == JV_KIND_STRING);
jv_free(as); jv search = default_search(jq, jv_object_get(dep, jv_string("search")));
as = jv_string(""); // dep is now freed; do not reuse
}
jv search = default_search(jv_object_get(dep, jv_string("search"))); // find_lib does a lot of work that could be cached...
jv_array_foreach(search, k, search_elt) { jv resolved = find_lib(jq, relpath, search, is_data ? ".json" : ".jq", jv_copy(jq_origin), jv_copy(lib_origin));
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);
// XXX ...move the rest of this into a callback. // XXX ...move the rest of this into a callback.
if (!jv_is_valid(lib_path)) { if (!jv_is_valid(resolved)) {
jv emsg = jv_invalid_get_msg(lib_path); jv emsg = jv_invalid_get_msg(resolved);
jq_report_error(jq, jv_string_fmt("jq: error: %s\n",jv_string_value(emsg))); jq_report_error(jq, jv_string_fmt("jq: error: %s\n",jv_string_value(emsg)));
jv_free(emsg); jv_free(emsg);
jv_free(as); 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; uint64_t state_idx = 0;
for (; state_idx < lib_state->ct; ++state_idx) { 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; break;
} }
if (state_idx < lib_state->ct) { // Found if (state_idx < lib_state->ct) { // Found
// XXX Check version matching here! jv_free(resolved);
if (version_matches(jq, bk, lib_state->defs[state_idx])) // Bind the library to the program
bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, jv_string_value(as)); 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);
} else { // Not found. Add it to the table before binding. } else { // Not found. Add it to the table before binding.
block dep_def_block = gen_noop(); 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) { if (nerrors == 0) {
// XXX Check version matching here! // Bind the library to the program
if (version_matches(jq, bk, dep_def_block)) bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
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)));
} }
} }
jv_free(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 // Loads the library at lib_path into lib_state, putting the library's defs
// into *out_block // 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; int nerrors = 0;
struct locfile* src; struct locfile* src = NULL;
block program; 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; 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))); src = locfile_init(jq, jv_string_value(data), jv_string_length_bytes(jv_copy(data)));
nerrors += jq_parse_library(src, &program); nerrors += jq_parse_library(src, &program);
if (nerrors == 0) { 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)); char *lib_origin = strdup(jv_string_value(lib_path));
nerrors += process_dependencies(jq, jq_get_jq_origin(jq), nerrors += process_dependencies(jq, jq_get_jq_origin(jq),
jv_string(dirname(lib_origin)), jv_string(dirname(lib_origin)),
&lib_state->defs[state_idx], lib_state); &program, lib_state);
free(lib_origin); 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(lib_path);
jv_free(data); jv_free(data);
return nerrors; 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 // FIXME It'd be nice to have an option to search the same search path
// as we do in process_dependencies. // as we do in process_dependencies.
jv load_module_meta(jq_state *jq, jv modname) { jv load_module_meta(jq_state *jq, jv mod_relpath) {
jv lib_path = find_lib(jq, modname, jv_array()); // 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 meta = jv_null();
jv data = jv_load_file(jv_string_value(lib_path), 1); jv data = jv_load_file(jv_string_value(lib_path), 1);
if (jv_is_valid(data)) { 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(); block libs = gen_noop();
for (uint64_t i = 0; i < lib_state.ct; ++i) { for (uint64_t i = 0; i < lib_state.ct; ++i) {
free(lib_state.names[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]); libs = block_join(libs, lib_state.defs[i]);
else else
block_free(lib_state.defs[i]); 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 & COLOUR_OUTPUT) dumpopts |= JV_PRINT_COLOUR;
if (options & NO_COLOUR_OUTPUT) dumpopts &= ~JV_PRINT_COLOUR; if (options & NO_COLOUR_OUTPUT) dumpopts &= ~JV_PRINT_COLOUR;
char *penv = getenv("JQ_LIBRARY_PATH"); if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) {
if (penv && jv_get_kind(lib_search_paths) == JV_KIND_NULL) { // Default search path list
// Use $JQ_LIBRARY_PATH lib_search_paths = JV_ARRAY(jv_string("~/.jq"),
#ifdef WIN32 jv_string("$ORIGIN/../lib/jq"),
#define PATH_ENV_SEPARATOR ";" jv_string("$ORIGIN/lib"));
#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
} }
jq_set_attr(jq, jv_string("JQ_LIBRARY_PATH"), lib_search_paths); 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(LOADV, VARIABLE, 1, 1)
OP(LOADVN, VARIABLE, 1, 1) OP(LOADVN, VARIABLE, 1, 1)
OP(STOREV, VARIABLE, 1, 0) OP(STOREV, VARIABLE, 1, 0)
OP(STORE_GLOBAL, GLOBAL, 0, 0)
OP(INDEX, NONE, 2, 1) OP(INDEX, NONE, 2, 1)
OP(INDEX_OPT, NONE, 2, 1) OP(INDEX_OPT, NONE, 2, 1)
OP(EACH, NONE, 1, 1) OP(EACH, NONE, 1, 1)
+33 -22
View File
@@ -280,14 +280,13 @@ Module:
%empty { %empty {
$$ = gen_noop(); $$ = gen_noop();
} | } |
"module" IDENT Exp ';' { "module" Exp ';' {
if (!block_is_const($3)) { if (!block_is_const($2)) {
FAIL(@$, "Module metadata must be constant."); FAIL(@$, "Module metadata must be constant.");
$$ = gen_noop(); $$ = gen_noop();
} else { } else {
$$ = gen_module(jv_string_value($2), $3); $$ = gen_module($2);
} }
jv_free($2);
} }
Imports: Imports:
@@ -479,33 +478,45 @@ Term {
} }
Import: Import:
"import" IDENT ';' { "import" String "as" '$' IDENT ';' {
$$ = gen_import(jv_string_value($2), gen_noop(), NULL); jv v = block_const($2);
jv_free($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 ';' { "import" String "as" IDENT ';' {
if (!block_is_const($3)) { jv v = block_const($2);
FAIL(@$, "Module metadata must be constant."); $$ = gen_import(jv_string_value(v), gen_noop(), jv_string_value($4), 0);
$$ = gen_noop(); block_free($2);
} 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);
jv_free($4); jv_free($4);
jv_free(v);
} | } |
"import" IDENT "as" IDENT Exp ';' { "import" String "as" IDENT Exp ';' {
if (!block_is_const($5)) { if (!block_is_const($5)) {
FAIL(@$, "Module metadata must be constant."); FAIL(@$, "Module metadata must be constant.");
$$ = gen_noop(); $$ = gen_noop();
} else { } 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); 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: FuncDef:
+1 -1
View File
@@ -1,2 +1,2 @@
module a {version:1.7}; module {version:1.7};
def a: "a"; def a: "a";
+9 -7
View File
@@ -1,12 +1,14 @@
module c {whatever:null}; module {whatever:null};
import a as foo; import "a" as foo;
import d as d {search:"./"}; import "d" as d {search:"./"};
import d {search:"./"}; import "d" as d2{search:"./"};
import e as e {search:"./../lib/jq"}; import "e" as e {search:"./../lib/jq"};
import f as f {search:"./../lib/jq"}; import "f" as f {search:"./../lib/jq"};
import "data" as $d;
def a: 0; def a: 0;
def c: 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 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 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") 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 fi
dd "if=tests/torture/input0.json" bs=$i count=1 2>/dev/null | 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 if [ -n "$VALGRIND" ]; then
grep '^==[0-9][0-9]*== ERROR SUMMARY: 0 errors' $d/err > /dev/null grep '^==[0-9][0-9]*== ERROR SUMMARY: 0 errors' $d/err > /dev/null
else else
@@ -200,22 +200,22 @@ if [ `HOME="$mods" $VALGRIND $Q ./jq --debug-dump-disasm -n fg | grep '^[a-z]' |
exit 1 exit 1
fi 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 echo "Module system appears to be broken" 1>&2
exit 1 exit 1
fi 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 echo "Module system appears to be broken" 1>&2
exit 1 exit 1
fi 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 echo "modulemeta builtin appears to be broken" 1>&2
exit 1 exit 1
fi 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 echo "Module system appears to be broken" 1>&2
exit 1 exit 1
fi fi