compile.c (jq-1.5) | : | compile.c (jq-1.6) | ||
---|---|---|---|---|
#ifndef _GNU_SOURCE | #ifndef _GNU_SOURCE | |||
#define _GNU_SOURCE // for strdup | #define _GNU_SOURCE // for strdup | |||
#endif | #endif | |||
#include <assert.h> | #include <assert.h> | |||
#include <math.h> | #include <math.h> | |||
#include <string.h> | #include <string.h> | |||
#include <stdlib.h> | #include <stdlib.h> | |||
#include <unistd.h> | ||||
#include "compile.h" | #include "compile.h" | |||
#include "bytecode.h" | #include "bytecode.h" | |||
#include "locfile.h" | #include "locfile.h" | |||
#include "jv_alloc.h" | #include "jv_alloc.h" | |||
#include "linker.h" | #include "linker.h" | |||
/* | /* | |||
The intermediate representation for jq filters is as a sequence of | The intermediate representation for jq filters is as a sequence of | |||
struct inst, which form a doubly-linked list via the next and prev | struct inst, which form a doubly-linked list via the next and prev | |||
pointers. | pointers. | |||
skipping to change at line 160 | skipping to change at line 161 | |||
block gen_const_global(jv constant, const char *name) { | block gen_const_global(jv constant, const char *name) { | |||
assert((opcode_describe(STORE_GLOBAL)->flags & (OP_HAS_CONSTANT | OP_HAS_VARIA BLE | OP_HAS_BINDING)) == | assert((opcode_describe(STORE_GLOBAL)->flags & (OP_HAS_CONSTANT | OP_HAS_VARIA BLE | OP_HAS_BINDING)) == | |||
(OP_HAS_CONSTANT | OP_HAS_VARIABLE | OP_HAS_BINDING)); | (OP_HAS_CONSTANT | OP_HAS_VARIABLE | OP_HAS_BINDING)); | |||
inst* i = inst_new(STORE_GLOBAL); | inst* i = inst_new(STORE_GLOBAL); | |||
i->imm.constant = constant; | i->imm.constant = constant; | |||
i->symbol = strdup(name); | i->symbol = strdup(name); | |||
return inst_block(i); | return inst_block(i); | |||
} | } | |||
block gen_op_pushk_under(jv constant) { | ||||
assert(opcode_describe(PUSHK_UNDER)->flags & OP_HAS_CONSTANT); | ||||
inst* i = inst_new(PUSHK_UNDER); | ||||
i->imm.constant = constant; | ||||
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 || b.first->op == PUSHK_UN DER)); | |||
} | } | |||
int block_is_const_inf(block b) { | int block_is_const_inf(block b) { | |||
return (block_is_single(b) && b.first->op == LOADK && | return (block_is_single(b) && b.first->op == LOADK && | |||
jv_get_kind(b.first->imm.constant) == JV_KIND_NUMBER && | jv_get_kind(b.first->imm.constant) == JV_KIND_NUMBER && | |||
isinf(jv_number_value(b.first->imm.constant))); | isinf(jv_number_value(b.first->imm.constant))); | |||
} | } | |||
jv_kind block_const_kind(block b) { | jv_kind block_const_kind(block b) { | |||
assert(block_is_const(b)); | assert(block_is_const(b)); | |||
skipping to change at line 221 | skipping to change at line 229 | |||
gen_noop(), OP_HAS_VARIABLE); | gen_noop(), OP_HAS_VARIABLE); | |||
} | } | |||
block gen_op_bound(opcode op, block binder) { | block gen_op_bound(opcode op, block binder) { | |||
assert(block_is_single(binder)); | assert(block_is_single(binder)); | |||
block b = gen_op_unbound(op, binder.first->symbol); | block b = gen_op_unbound(op, binder.first->symbol); | |||
b.first->bound_by = binder.first; | b.first->bound_by = binder.first; | |||
return b; | return b; | |||
} | } | |||
block gen_dictpair(block k, block v) { | ||||
return BLOCK(gen_subexp(k), gen_subexp(v), gen_op_simple(INSERT)); | ||||
} | ||||
static void inst_join(inst* a, inst* b) { | static void inst_join(inst* a, inst* b) { | |||
assert(a && b); | assert(a && b); | |||
assert(!a->next); | assert(!a->next); | |||
assert(!b->prev); | assert(!b->prev); | |||
a->next = b; | a->next = b; | |||
b->prev = a; | b->prev = a; | |||
} | } | |||
void block_append(block* b, block b2) { | void block_append(block* b, block b2) { | |||
if (b2.first) { | if (b2.first) { | |||
skipping to change at line 481 | skipping to change at line 493 | |||
imports = jv_array_append(imports, jv_copy(dep->imm.constant)); | imports = jv_array_append(imports, jv_copy(dep->imm.constant)); | |||
} | } | |||
inst_free(dep); | inst_free(dep); | |||
} | } | |||
if (top) { | if (top) { | |||
*body = block_join(inst_block(top),*body); | *body = block_join(inst_block(top),*body); | |||
} | } | |||
return imports; | return imports; | |||
} | } | |||
jv block_list_funcs(block body, int omit_underscores) { | ||||
jv funcs = jv_object(); // Use the keys for set semantics. | ||||
for (inst *pos = body.first; pos != NULL; pos = pos->next) { | ||||
if (pos->op == CLOSURE_CREATE || pos->op == CLOSURE_CREATE_C) { | ||||
if (pos->symbol != NULL && (!omit_underscores || pos->symbol[0] != '_')) { | ||||
funcs = jv_object_set(funcs, jv_string_fmt("%s/%i", pos->symbol, pos->nf | ||||
ormals), jv_null()); | ||||
} | ||||
} | ||||
} | ||||
return jv_keys_unsorted(funcs); | ||||
} | ||||
block gen_module(block metadata) { | block gen_module(block metadata) { | |||
inst* i = inst_new(MODULEMETA); | inst* i = inst_new(MODULEMETA); | |||
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.c onstant); | i->imm.constant = jv_object_set(jv_object(), jv_string("metadata"), i->imm.c onstant); | |||
block_free(metadata); | block_free(metadata); | |||
return inst_block(i); | return inst_block(i); | |||
} | } | |||
jv block_module_meta(block b) { | jv block_module_meta(block b) { | |||
if (b.first != NULL && b.first->op == MODULEMETA) | if (b.first != NULL && b.first->op == MODULEMETA) | |||
return jv_copy(b.first->imm.constant); | return jv_copy(b.first->imm.constant); | |||
return jv_null(); | return jv_null(); | |||
} | } | |||
block gen_import(const char* name, block metadata, const char* as, int is_data) | block gen_import(const char* name, const char* as, int is_data) { | |||
{ | ||||
assert(metadata.first == NULL || block_is_const(metadata)); | ||||
inst* i = inst_new(DEPS); | inst* i = inst_new(DEPS); | |||
jv meta; | jv meta = jv_object(); | |||
if (block_is_const(metadata)) | ||||
meta = block_const(metadata); | ||||
else | ||||
meta = jv_object(); | ||||
if (as != NULL) | if (as != NULL) | |||
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_fals e()); | meta = jv_object_set(meta, jv_string("is_data"), is_data ? jv_true() : jv_fals e()); | |||
meta = jv_object_set(meta, jv_string("relpath"), jv_string(name)); | meta = jv_object_set(meta, jv_string("relpath"), jv_string(name)); | |||
i->imm.constant = meta; | i->imm.constant = meta; | |||
block_free(metadata); | ||||
return inst_block(i); | return inst_block(i); | |||
} | } | |||
block gen_import_meta(block import, block metadata) { | ||||
assert(block_is_single(import) && import.first->op == DEPS); | ||||
assert(block_is_const(metadata) && block_const_kind(metadata) == JV_KIND_OBJEC | ||||
T); | ||||
inst *i = import.first; | ||||
i->imm.constant = jv_object_merge(block_const(metadata), i->imm.constant); | ||||
block_free(metadata); | ||||
return import; | ||||
} | ||||
block gen_function(const char* name, block formals, block body) { | block gen_function(const char* name, block formals, block body) { | |||
inst* i = inst_new(CLOSURE_CREATE); | inst* i = inst_new(CLOSURE_CREATE); | |||
for (inst* i = formals.last; i; i = i->prev) { | for (inst* i = formals.last; i; i = i->prev) { | |||
if (i->op == CLOSURE_PARAM_REGULAR) { | if (i->op == CLOSURE_PARAM_REGULAR) { | |||
i->op = CLOSURE_PARAM; | i->op = CLOSURE_PARAM; | |||
body = gen_var_binding(gen_call(i->symbol, gen_noop()), i->symbol, body); | body = gen_var_binding(gen_call(i->symbol, gen_noop()), i->symbol, body); | |||
} | } | |||
block_bind_subblock(inst_block(i), body, OP_IS_CALL_PSEUDO | OP_HAS_BINDING, 0); | block_bind_subblock(inst_block(i), body, OP_IS_CALL_PSEUDO | OP_HAS_BINDING, 0); | |||
} | } | |||
i->subfn = body; | i->subfn = body; | |||
skipping to change at line 549 | skipping to change at line 576 | |||
return gen_function("@lambda", gen_noop(), body); | return gen_function("@lambda", gen_noop(), body); | |||
} | } | |||
block gen_call(const char* name, block args) { | block gen_call(const char* name, block args) { | |||
block b = gen_op_unbound(CALL_JQ, name); | block b = gen_op_unbound(CALL_JQ, name); | |||
b.first->arglist = args; | b.first->arglist = args; | |||
return b; | return b; | |||
} | } | |||
block gen_subexp(block a) { | block gen_subexp(block a) { | |||
if (block_is_noop(a)) { | ||||
return gen_op_simple(DUP); | ||||
} | ||||
if (block_is_single(a) && a.first->op == LOADK) { | ||||
jv c = block_const(a); | ||||
block_free(a); | ||||
return gen_op_pushk_under(c); | ||||
} | ||||
return BLOCK(gen_op_simple(SUBEXP_BEGIN), a, gen_op_simple(SUBEXP_END)); | return BLOCK(gen_op_simple(SUBEXP_BEGIN), a, gen_op_simple(SUBEXP_END)); | |||
} | } | |||
block gen_both(block a, block b) { | block gen_both(block a, block b) { | |||
block jump = gen_op_targetlater(JUMP); | block jump = gen_op_targetlater(JUMP); | |||
block fork = gen_op_target(FORK, jump); | block fork = gen_op_target(FORK, jump); | |||
block c = BLOCK(fork, a, jump, b); | block c = BLOCK(fork, a, jump, b); | |||
inst_set_target(jump, c); | inst_set_target(jump, c); | |||
return c; | return c; | |||
} | } | |||
block gen_const_object(block expr) { | block gen_const_object(block expr) { | |||
int is_const = 1; | int is_const = 1; | |||
jv o = jv_object(); | jv o = jv_object(); | |||
jv k = jv_null(); | jv k = jv_null(); | |||
jv v = jv_null(); | jv v = jv_null(); | |||
for (inst *i = expr.first; i; i = i->next) { | for (inst *i = expr.first; i; i = i->next) { | |||
if (i->op != SUBEXP_BEGIN || | if (i->op == PUSHK_UNDER) { | |||
k = jv_copy(i->imm.constant); | ||||
i = i->next; | ||||
} else if (i->op != SUBEXP_BEGIN || | ||||
i->next == NULL || | i->next == NULL || | |||
i->next->op != LOADK || | i->next->op != LOADK || | |||
i->next->next == NULL || | i->next->next == NULL || | |||
i->next->next->op != SUBEXP_END) { | i->next->next->op != SUBEXP_END) { | |||
is_const = 0; | is_const = 0; | |||
break; | break; | |||
} else { | ||||
k = jv_copy(i->next->imm.constant); | ||||
i = i->next->next->next; | ||||
} | } | |||
k = jv_copy(i->next->imm.constant); | if (i != NULL && i->op == PUSHK_UNDER) { | |||
i = i->next->next->next; | v = jv_copy(i->imm.constant); | |||
if (i == NULL || | i = i->next; | |||
} else if (i == NULL || | ||||
i->op != SUBEXP_BEGIN || | i->op != SUBEXP_BEGIN || | |||
i->next == NULL || | i->next == NULL || | |||
i->next->op != LOADK || | i->next->op != LOADK || | |||
i->next->next == NULL || | i->next->next == NULL || | |||
i->next->next->op != SUBEXP_END) { | i->next->next->op != SUBEXP_END) { | |||
is_const = 0; | is_const = 0; | |||
break; | break; | |||
} else { | ||||
v = jv_copy(i->next->imm.constant); | ||||
i = i->next->next->next; | ||||
} | } | |||
v = jv_copy(i->next->imm.constant); | ||||
i = i->next->next->next; | ||||
if (i == NULL || i->op != INSERT) { | if (i == NULL || i->op != INSERT) { | |||
is_const = 0; | is_const = 0; | |||
break; | break; | |||
} | } | |||
if (jv_get_kind(k) != JV_KIND_STRING) { | ||||
is_const = 0; | ||||
break; | ||||
} | ||||
o = jv_object_set(o, k, v); | o = jv_object_set(o, k, v); | |||
k = jv_null(); | k = jv_null(); | |||
v = jv_null(); | v = jv_null(); | |||
} | } | |||
if (!is_const) { | if (!is_const) { | |||
jv_free(o); | jv_free(o); | |||
jv_free(k); | jv_free(k); | |||
jv_free(v); | jv_free(v); | |||
block b = {0,0}; | block b = {0,0}; | |||
return b; | return b; | |||
skipping to change at line 688 | skipping to change at line 735 | |||
expr, | expr, | |||
tail, | tail, | |||
gen_op_bound(LOADVN, array_var)); | gen_op_bound(LOADVN, array_var)); | |||
} | } | |||
static block bind_matcher(block matcher, block body) { | static block bind_matcher(block matcher, block body) { | |||
// cannot call block_bind(matcher, body) because that requires | // cannot call block_bind(matcher, body) because that requires | |||
// block_has_only_binders(matcher), which is not true here as matchers | // block_has_only_binders(matcher), which is not true here as matchers | |||
// may also contain code to extract the correct elements | // may also contain code to extract the correct elements | |||
for (inst* i = matcher.first; i; i = i->next) { | for (inst* i = matcher.first; i; i = i->next) { | |||
if (i->op == STOREV && !i->bound_by) | if ((i->op == STOREV || i->op == STOREVN) && !i->bound_by) | |||
block_bind_subblock(inst_block(i), body, OP_HAS_VARIABLE, 0); | block_bind_subblock(inst_block(i), body, OP_HAS_VARIABLE, 0); | |||
} | } | |||
return BLOCK(matcher, body); | return BLOCK(matcher, body); | |||
} | } | |||
// Extract destructuring var names from the block | ||||
// *vars should be a jv_object (for set semantics) | ||||
static void block_get_unbound_vars(block b, jv *vars) { | ||||
assert(vars != NULL); | ||||
assert(jv_get_kind(*vars) == JV_KIND_OBJECT); | ||||
for (inst* i = b.first; i; i = i->next) { | ||||
if (i->subfn.first) { | ||||
block_get_unbound_vars(i->subfn, vars); | ||||
continue; | ||||
} | ||||
if ((i->op == STOREV || i->op == STOREVN) && i->bound_by == NULL) { | ||||
*vars = jv_object_set(*vars, jv_string(i->symbol), jv_true()); | ||||
} | ||||
} | ||||
} | ||||
/* Build wrappers around destructuring matchers so that we can chain them | ||||
* when we have errors. The approach is as follows: | ||||
* DESTRUCTURE_ALT NEXT_MATCHER (unless last matcher) | ||||
* existing_matcher_block | ||||
* JUMP BODY | ||||
*/ | ||||
static block bind_alternation_matchers(block matchers, block body) { | ||||
block preamble = {0}; | ||||
block altmatchers = {0}; | ||||
block mb = {0}; | ||||
block final_matcher = matchers; | ||||
// Pass through the matchers to find all destructured names. | ||||
while (final_matcher.first && final_matcher.first->op == DESTRUCTURE_ALT) { | ||||
block_append(&altmatchers, inst_block(block_take(&final_matcher))); | ||||
} | ||||
// We don't have any alternations here, so we can use the simplest case. | ||||
if (altmatchers.first == NULL) { | ||||
return bind_matcher(final_matcher, body); | ||||
} | ||||
// Collect var names | ||||
jv all_vars = jv_object(); | ||||
block_get_unbound_vars(altmatchers, &all_vars); | ||||
block_get_unbound_vars(final_matcher, &all_vars); | ||||
// We need a preamble of STOREVs to which to bind the matchers and the body. | ||||
jv_object_keys_foreach(all_vars, key) { | ||||
preamble = BLOCK(preamble, | ||||
gen_op_simple(DUP), | ||||
gen_const(jv_null()), | ||||
gen_op_unbound(STOREV, jv_string_value(key))); | ||||
jv_free(key); | ||||
} | ||||
jv_free(all_vars); | ||||
// Now we build each matcher in turn | ||||
for (inst *i = altmatchers.first; i; i = i->next) { | ||||
block submatcher = i->subfn; | ||||
// If we're successful, jump to the end of the matchers | ||||
submatcher = BLOCK(submatcher, gen_op_target(JUMP, final_matcher)); | ||||
// DESTRUCTURE_ALT to the end of this submatcher so we can skip to the next | ||||
one on error | ||||
mb = BLOCK(mb, gen_op_target(DESTRUCTURE_ALT, submatcher), submatcher); | ||||
// We're done with this inst and we don't want it anymore | ||||
// But we can't let it free the submatcher block. | ||||
i->subfn.first = i->subfn.last = NULL; | ||||
} | ||||
// We're done with these insts now. | ||||
block_free(altmatchers); | ||||
return bind_matcher(preamble, BLOCK(mb, final_matcher, body)); | ||||
} | ||||
block gen_reduce(block source, block matcher, block init, block body) { | block gen_reduce(block source, block matcher, block init, block body) { | |||
block res_var = gen_op_var_fresh(STOREV, "reduce"); | block res_var = gen_op_var_fresh(STOREV, "reduce"); | |||
block loop = BLOCK(gen_op_simple(DUPN), | block loop = BLOCK(gen_op_simple(DUPN), | |||
source, | source, | |||
bind_matcher(matcher, | bind_alternation_matchers(matcher, | |||
BLOCK(gen_op_bound(LOADVN, res_var), | BLOCK(gen_op_bound(LOADVN, res_var), | |||
body, | body, | |||
gen_op_bound(STOREV, res_var))), | gen_op_bound(STOREV, res_var))), | |||
gen_op_simple(BACKTRACK)); | gen_op_simple(BACKTRACK)); | |||
return BLOCK(gen_op_simple(DUP), | return BLOCK(gen_op_simple(DUP), | |||
init, | init, | |||
res_var, | res_var, | |||
gen_op_target(FORK, loop), | gen_op_target(FORK, loop), | |||
loop, | loop, | |||
gen_op_bound(LOADVN, res_var)); | gen_op_bound(LOADVN, res_var)); | |||
} | } | |||
block gen_foreach(block source, block matcher, block init, block update, block e xtract) { | block gen_foreach(block source, block matcher, block init, block update, block e xtract) { | |||
block output = gen_op_targetlater(JUMP); | block output = gen_op_targetlater(JUMP); | |||
block state_var = gen_op_var_fresh(STOREV, "foreach"); | block state_var = gen_op_var_fresh(STOREV, "foreach"); | |||
block loop = BLOCK(gen_op_simple(DUPN), | block loop = BLOCK(gen_op_simple(DUPN), | |||
// get a value from the source expression: | // get a value from the source expression: | |||
source, | source, | |||
// destructure the value into variable(s) for all the code | // destructure the value into variable(s) for all the code | |||
// in the body to see | // in the body to see | |||
bind_matcher(matcher, | bind_alternation_matchers(matcher, | |||
// load the loop state variable | // load the loop state variable | |||
BLOCK(gen_op_bound(LOADVN, state_var), | BLOCK(gen_op_bound(LOADVN, state_var), | |||
// generate updated state | // generate updated state | |||
update, | update, | |||
// save the updated state for value extr action | // save the updated state for value extr action | |||
gen_op_simple(DUP), | gen_op_simple(DUP), | |||
// save new state | // save new state | |||
gen_op_bound(STOREV, state_var), | gen_op_bound(STOREV, state_var), | |||
// extract an output... | // extract an output... | |||
extract, | extract, | |||
skipping to change at line 825 | skipping to change at line 945 | |||
block gen_or(block a, block b) { | block gen_or(block a, block b) { | |||
// a or b = if a then true else (if b then true else false) | // a or b = if a then true else (if b then true else false) | |||
return BLOCK(gen_op_simple(DUP), a, | return BLOCK(gen_op_simple(DUP), a, | |||
gen_condbranch(BLOCK(gen_op_simple(POP), gen_const(jv_true())), | gen_condbranch(BLOCK(gen_op_simple(POP), gen_const(jv_true())), | |||
BLOCK(gen_op_simple(POP), | BLOCK(gen_op_simple(POP), | |||
b, | b, | |||
gen_condbranch(gen_const(jv_true()), | gen_condbranch(gen_const(jv_true()), | |||
gen_const(jv_false()))))); | gen_const(jv_false()))))); | |||
} | } | |||
block gen_destructure_alt(block matcher) { | ||||
for (inst *i = matcher.first; i; i = i->next) { | ||||
if (i->op == STOREV) { | ||||
i->op = STOREVN; | ||||
} | ||||
} | ||||
inst* i = inst_new(DESTRUCTURE_ALT); | ||||
i->subfn = matcher; | ||||
return inst_block(i); | ||||
} | ||||
block gen_var_binding(block var, const char* name, block body) { | block gen_var_binding(block var, const char* name, block body) { | |||
return gen_destructure(var, gen_op_unbound(STOREV, name), body); | return gen_destructure(var, gen_op_unbound(STOREV, name), body); | |||
} | } | |||
block gen_array_matcher(block left, block curr) { | block gen_array_matcher(block left, block curr) { | |||
int index; | int index; | |||
if (block_is_noop(left)) | if (block_is_noop(left)) | |||
index = 0; | index = 0; | |||
else { | else { | |||
// `left` was returned by this function, so the third inst is the | // `left` was returned by this function, so the third inst is the | |||
// constant containing the previously used index | // constant containing the previously used index | |||
assert(left.first->op == DUP); | assert(left.first->op == DUP); | |||
assert(left.first->next->op == SUBEXP_BEGIN); | assert(left.first->next != NULL); | |||
assert(left.first->next->next->op == LOADK); | inst *i = NULL; | |||
index = 1 + (int) jv_number_value(left.first->next->next->imm.constant); | if (left.first->next->op == PUSHK_UNDER) { | |||
i = left.first->next; | ||||
} else { | ||||
assert(left.first->next->op == SUBEXP_BEGIN); | ||||
assert(left.first->next->next->op == LOADK); | ||||
i = left.first->next->next; | ||||
} | ||||
index = 1 + (int) jv_number_value(i->imm.constant); | ||||
} | } | |||
// `left` goes at the end so that the const index is in a predictable place | // `left` goes at the end so that the const index is in a predictable place | |||
return BLOCK(gen_op_simple(DUP), gen_subexp(gen_const(jv_number(index))), | return BLOCK(gen_op_simple(DUP), gen_subexp(gen_const(jv_number(index))), | |||
gen_op_simple(INDEX), curr, left); | gen_op_simple(INDEX), curr, left); | |||
} | } | |||
block gen_object_matcher(block name, block curr) { | block gen_object_matcher(block name, block curr) { | |||
return BLOCK(gen_op_simple(DUP), gen_subexp(name), gen_op_simple(INDEX), | return BLOCK(gen_op_simple(DUP), gen_subexp(name), gen_op_simple(INDEX), | |||
curr); | curr); | |||
} | } | |||
block gen_destructure(block var, block matcher, block body) { | block gen_destructure(block var, block matchers, block body) { | |||
// var bindings can be added after coding the program; leave the TOP first. | // var bindings can be added after coding the program; leave the TOP first. | |||
block top = gen_noop(); | block top = gen_noop(); | |||
if (body.first && body.first->op == TOP) | if (body.first && body.first->op == TOP) | |||
top = inst_block(block_take(&body)); | top = inst_block(block_take(&body)); | |||
return BLOCK(top, gen_op_simple(DUP), var, bind_matcher(matcher, body)); | if (matchers.first && matchers.first->op == DESTRUCTURE_ALT) { | |||
block_append(&var, gen_op_simple(DUP)); | ||||
} else { | ||||
top = BLOCK(top, gen_op_simple(DUP)); | ||||
} | ||||
return BLOCK(top, gen_subexp(var), gen_op_simple(POP), bind_alternation_matche | ||||
rs(matchers, body)); | ||||
} | } | |||
// Like gen_var_binding(), but bind `break`'s wildcard unbound variable | // Like gen_var_binding(), but bind `break`'s wildcard unbound variable | |||
static block gen_wildvar_binding(block var, const char* name, block body) { | static block gen_wildvar_binding(block var, const char* name, block body) { | |||
return BLOCK(gen_op_simple(DUP), var, | return BLOCK(gen_op_simple(DUP), var, | |||
block_bind(gen_op_unbound(STOREV, name), | block_bind(gen_op_unbound(STOREV, name), | |||
body, OP_HAS_VARIABLE | OP_BIND_WILDCARD)); | body, OP_HAS_VARIABLE | OP_BIND_WILDCARD)); | |||
} | } | |||
block gen_cond(block cond, block iftrue, block iffalse) { | block gen_cond(block cond, block iftrue, block iffalse) { | |||
return BLOCK(gen_op_simple(DUP), cond, | return BLOCK(gen_op_simple(DUP), BLOCK(gen_subexp(cond), gen_op_simple(POP)), | |||
gen_condbranch(BLOCK(gen_op_simple(POP), iftrue), | gen_condbranch(BLOCK(gen_op_simple(POP), iftrue), | |||
BLOCK(gen_op_simple(POP), iffalse))); | BLOCK(gen_op_simple(POP), iffalse))); | |||
} | } | |||
block gen_try_handler(block handler) { | block gen_try_handler(block handler) { | |||
// Quite a pain just to hide jq's internal errors. | // Quite a pain just to hide jq's internal errors. | |||
return gen_cond(// `if type=="object" and .__jq | return gen_cond(// `if type=="object" and .__jq | |||
gen_and(gen_call("_equal", | gen_and(gen_call("_equal", | |||
BLOCK(gen_lambda(gen_const(jv_string("object" ))), | BLOCK(gen_lambda(gen_const(jv_string("object" ))), | |||
gen_lambda(gen_noop()))), | gen_lambda(gen_noop()))), | |||
skipping to change at line 962 | skipping to change at line 1106 | |||
static int count_cfunctions(block b) { | static int count_cfunctions(block b) { | |||
int n = 0; | int n = 0; | |||
for (inst* i = b.first; i; i = i->next) { | for (inst* i = b.first; i; i = i->next) { | |||
if (i->op == CLOSURE_CREATE_C) n++; | if (i->op == CLOSURE_CREATE_C) n++; | |||
n += count_cfunctions(i->subfn); | n += count_cfunctions(i->subfn); | |||
} | } | |||
return n; | return n; | |||
} | } | |||
#ifndef WIN32 | ||||
extern char **environ; | ||||
#endif | ||||
static jv | ||||
make_env(jv env) | ||||
{ | ||||
if (jv_is_valid(env)) | ||||
return jv_copy(env); | ||||
jv r = jv_object(); | ||||
if (environ == NULL) | ||||
return r; | ||||
for (size_t i = 0; environ[i] != NULL; i++) { | ||||
const char *eq; | ||||
if ((eq = strchr(environ[i], '=')) == NULL) | ||||
r = jv_object_delete(r, jv_string(environ[i])); | ||||
else | ||||
r = jv_object_set(r, jv_string_sized(environ[i], eq - environ[i]), jv_stri | ||||
ng(eq + 1)); | ||||
} | ||||
return jv_copy(r); | ||||
} | ||||
// Expands call instructions into a calling sequence | // Expands call instructions into a calling sequence | |||
static int expand_call_arglist(block* b) { | static int expand_call_arglist(block* b, jv args, jv *env) { | |||
int errors = 0; | int errors = 0; | |||
block ret = gen_noop(); | block ret = gen_noop(); | |||
for (inst* curr; (curr = block_take(b));) { | for (inst* curr; (curr = block_take(b));) { | |||
if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) { | if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) { | |||
if (!curr->bound_by) { | if (!curr->bound_by && curr->op == LOADV && strcmp(curr->symbol, "ENV") == | |||
0) { | ||||
curr->op = LOADK; | ||||
*env = curr->imm.constant = make_env(*env); | ||||
} else if (!curr->bound_by && curr->op == LOADV && jv_object_has(jv_copy(a | ||||
rgs), jv_string(curr->symbol))) { | ||||
curr->op = LOADK; | ||||
curr->imm.constant = jv_object_get(jv_copy(args), jv_string(curr->symbol | ||||
)); | ||||
} else if (!curr->bound_by) { | ||||
if (curr->symbol[0] == '*' && curr->symbol[1] >= '1' && curr->symbol[1] <= '3' && curr->symbol[2] == '\0') | if (curr->symbol[0] == '*' && curr->symbol[1] >= '1' && curr->symbol[1] <= '3' && curr->symbol[2] == '\0') | |||
locfile_locate(curr->locfile, curr->source, "jq: error: break used out side labeled control structure"); | locfile_locate(curr->locfile, curr->source, "jq: error: break used out side labeled control structure"); | |||
else if (curr->op == LOADV) | ||||
locfile_locate(curr->locfile, curr->source, "jq: error: $%s is not def | ||||
ined", curr->symbol); | ||||
else | else | |||
locfile_locate(curr->locfile, curr->source, "jq: error: %s/%d is not d efined", curr->symbol, block_count_actuals(curr->arglist)); | locfile_locate(curr->locfile, curr->source, "jq: error: %s/%d is not d efined", curr->symbol, block_count_actuals(curr->arglist)); | |||
errors++; | errors++; | |||
// don't process this instruction if it's not well-defined | // don't process this instruction if it's not well-defined | |||
ret = BLOCK(ret, inst_block(curr)); | ret = BLOCK(ret, inst_block(curr)); | |||
continue; | continue; | |||
} | } | |||
} | } | |||
block prelude = gen_noop(); | block prelude = gen_noop(); | |||
skipping to change at line 1023 | skipping to change at line 1198 | |||
break; | break; | |||
} | } | |||
case CLOSURE_CREATE_C: { | case CLOSURE_CREATE_C: { | |||
for (inst* i; (i = block_take(&curr->arglist)); ) { | for (inst* i; (i = block_take(&curr->arglist)); ) { | |||
assert(i->op == CLOSURE_CREATE); // FIXME | assert(i->op == CLOSURE_CREATE); // FIXME | |||
block body = i->subfn; | block body = i->subfn; | |||
i->subfn = gen_noop(); | i->subfn = gen_noop(); | |||
inst_free(i); | inst_free(i); | |||
// arguments should be pushed in reverse order, prepend them to prelud e | // arguments should be pushed in reverse order, prepend them to prelud e | |||
errors += expand_call_arglist(&body); | errors += expand_call_arglist(&body, args, env); | |||
prelude = BLOCK(gen_subexp(body), prelude); | prelude = BLOCK(gen_subexp(body), prelude); | |||
actual_args++; | actual_args++; | |||
} | } | |||
assert(curr->op == CALL_JQ); | assert(curr->op == CALL_JQ); | |||
curr->op = CALL_BUILTIN; | curr->op = CALL_BUILTIN; | |||
curr->imm.intval = actual_args + 1 /* include the implicit input in arg count */; | curr->imm.intval = actual_args + 1 /* include the implicit input in arg count */; | |||
assert(curr->bound_by->op == CLOSURE_CREATE_C); | assert(curr->bound_by->op == CLOSURE_CREATE_C); | |||
desired_args = curr->bound_by->imm.cfunc->nargs - 1; | desired_args = curr->bound_by->imm.cfunc->nargs - 1; | |||
assert(!curr->arglist.first); | assert(!curr->arglist.first); | |||
break; | break; | |||
skipping to change at line 1045 | skipping to change at line 1220 | |||
} | } | |||
assert(actual_args == desired_args); // because now handle this above | assert(actual_args == desired_args); // because now handle this above | |||
} | } | |||
ret = BLOCK(ret, prelude, inst_block(curr)); | ret = BLOCK(ret, prelude, inst_block(curr)); | |||
} | } | |||
*b = ret; | *b = ret; | |||
return errors; | return errors; | |||
} | } | |||
static int compile(struct bytecode* bc, block b, struct locfile* lf) { | static int compile(struct bytecode* bc, block b, struct locfile* lf, jv args, jv *env) { | |||
int errors = 0; | int errors = 0; | |||
int pos = 0; | int pos = 0; | |||
int var_frame_idx = 0; | int var_frame_idx = 0; | |||
bc->nsubfunctions = 0; | bc->nsubfunctions = 0; | |||
errors += expand_call_arglist(&b); | errors += expand_call_arglist(&b, args, env); | |||
b = BLOCK(b, gen_op_simple(RET)); | b = BLOCK(b, gen_op_simple(RET)); | |||
jv localnames = jv_array(); | jv localnames = jv_array(); | |||
for (inst* curr = b.first; curr; curr = curr->next) { | for (inst* curr = b.first; curr; curr = curr->next) { | |||
if (!curr->next) assert(curr == b.last); | if (!curr->next) assert(curr == b.last); | |||
int length = opcode_describe(curr->op)->length; | int length = opcode_describe(curr->op)->length; | |||
if (curr->op == CALL_JQ) { | if (curr->op == CALL_JQ) { | |||
for (inst* arg = curr->arglist.first; arg; arg = arg->next) { | for (inst* arg = curr->arglist.first; arg; arg = arg->next) { | |||
length += 2; | length += 2; | |||
} | } | |||
} | } | |||
skipping to change at line 1095 | skipping to change at line 1270 | |||
} | } | |||
if (pos > 0xFFFF) { | if (pos > 0xFFFF) { | |||
// too long for program counter to fit in uint16_t | // too long for program counter to fit in uint16_t | |||
locfile_locate(lf, UNKNOWN_LOCATION, | locfile_locate(lf, UNKNOWN_LOCATION, | |||
"function compiled to %d bytes which is too long", pos); | "function compiled to %d bytes which is too long", pos); | |||
errors++; | errors++; | |||
} | } | |||
bc->codelen = pos; | bc->codelen = pos; | |||
bc->debuginfo = jv_object_set(bc->debuginfo, jv_string("locals"), localnames); | bc->debuginfo = jv_object_set(bc->debuginfo, jv_string("locals"), localnames); | |||
if (bc->nsubfunctions) { | if (bc->nsubfunctions) { | |||
bc->subfunctions = jv_mem_alloc(sizeof(struct bytecode*) * bc->nsubfunctions ); | bc->subfunctions = jv_mem_calloc(sizeof(struct bytecode*), bc->nsubfunctions ); | |||
for (inst* curr = b.first; curr; curr = curr->next) { | for (inst* curr = b.first; curr; curr = curr->next) { | |||
if (curr->op == CLOSURE_CREATE) { | if (curr->op == CLOSURE_CREATE) { | |||
struct bytecode* subfn = jv_mem_alloc(sizeof(struct bytecode)); | struct bytecode* subfn = jv_mem_alloc(sizeof(struct bytecode)); | |||
bc->subfunctions[curr->imm.intval] = subfn; | bc->subfunctions[curr->imm.intval] = subfn; | |||
subfn->globals = bc->globals; | subfn->globals = bc->globals; | |||
subfn->parent = bc; | subfn->parent = bc; | |||
subfn->nclosures = 0; | subfn->nclosures = 0; | |||
subfn->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_stri ng(curr->symbol)); | subfn->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_stri ng(curr->symbol)); | |||
jv params = jv_array(); | jv params = jv_array(); | |||
for (inst* param = curr->arglist.first; param; param = param->next) { | for (inst* param = curr->arglist.first; param; param = param->next) { | |||
assert(param->op == CLOSURE_PARAM); | assert(param->op == CLOSURE_PARAM); | |||
assert(param->bound_by == param); | assert(param->bound_by == param); | |||
param->imm.intval = subfn->nclosures++; | param->imm.intval = subfn->nclosures++; | |||
param->compiled = subfn; | param->compiled = subfn; | |||
params = jv_array_append(params, jv_string(param->symbol)); | params = jv_array_append(params, jv_string(param->symbol)); | |||
} | } | |||
subfn->debuginfo = jv_object_set(subfn->debuginfo, jv_string("params"), params); | subfn->debuginfo = jv_object_set(subfn->debuginfo, jv_string("params"), params); | |||
errors += compile(subfn, curr->subfn, lf); | errors += compile(subfn, curr->subfn, lf, args, env); | |||
curr->subfn = gen_noop(); | curr->subfn = gen_noop(); | |||
} | } | |||
} | } | |||
} else { | } else { | |||
bc->subfunctions = 0; | bc->subfunctions = 0; | |||
} | } | |||
uint16_t* code = jv_mem_alloc(sizeof(uint16_t) * bc->codelen); | uint16_t* code = jv_mem_calloc(sizeof(uint16_t), bc->codelen); | |||
bc->code = code; | bc->code = code; | |||
pos = 0; | pos = 0; | |||
jv constant_pool = jv_array(); | jv constant_pool = jv_array(); | |||
int maxvar = -1; | int maxvar = -1; | |||
if (!errors) for (inst* curr = b.first; curr; curr = curr->next) { | if (!errors) for (inst* curr = b.first; curr; curr = curr->next) { | |||
const struct opcode_description* op = opcode_describe(curr->op); | const struct opcode_description* op = opcode_describe(curr->op); | |||
if (op->length == 0) | if (op->length == 0) | |||
continue; | continue; | |||
code[pos++] = curr->op; | code[pos++] = curr->op; | |||
assert(curr->op != CLOSURE_REF && curr->op != CLOSURE_PARAM); | assert(curr->op != CLOSURE_REF && curr->op != CLOSURE_PARAM); | |||
skipping to change at line 1178 | skipping to change at line 1353 | |||
} else if (op->length > 1) { | } else if (op->length > 1) { | |||
assert(0 && "codegen not implemented for this operation"); | assert(0 && "codegen not implemented for this operation"); | |||
} | } | |||
} | } | |||
bc->constants = constant_pool; | bc->constants = constant_pool; | |||
bc->nlocals = maxvar + 2; // FIXME: frames of size zero? | bc->nlocals = maxvar + 2; // FIXME: frames of size zero? | |||
block_free(b); | block_free(b); | |||
return errors; | return errors; | |||
} | } | |||
int block_compile(block b, struct bytecode** out, struct locfile* lf) { | int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) { | |||
struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode)); | struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode)); | |||
bc->parent = 0; | bc->parent = 0; | |||
bc->nclosures = 0; | bc->nclosures = 0; | |||
bc->globals = jv_mem_alloc(sizeof(struct symbol_table)); | bc->globals = jv_mem_alloc(sizeof(struct symbol_table)); | |||
int ncfunc = count_cfunctions(b); | int ncfunc = count_cfunctions(b); | |||
bc->globals->ncfunctions = 0; | bc->globals->ncfunctions = 0; | |||
bc->globals->cfunctions = jv_mem_alloc(sizeof(struct cfunction) * ncfunc); | bc->globals->cfunctions = jv_mem_calloc(sizeof(struct cfunction), ncfunc); | |||
bc->globals->cfunc_names = jv_array(); | bc->globals->cfunc_names = jv_array(); | |||
bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null()); | bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null()); | |||
int nerrors = compile(bc, b, lf); | jv env = jv_invalid(); | |||
int nerrors = compile(bc, b, lf, args, &env); | ||||
jv_free(args); | ||||
jv_free(env); | ||||
assert(bc->globals->ncfunctions == ncfunc); | assert(bc->globals->ncfunctions == ncfunc); | |||
if (nerrors > 0) { | if (nerrors > 0) { | |||
bytecode_free(bc); | bytecode_free(bc); | |||
*out = 0; | *out = 0; | |||
} else { | } else { | |||
*out = bc; | *out = bc; | |||
} | } | |||
return nerrors; | return nerrors; | |||
} | } | |||
End of changes. 38 change blocks. | ||||
36 lines changed or deleted | 222 lines changed or added |