1
0
mirror of https://github.com/stedolan/jq.git synced 2024-05-11 05:55:39 +00:00

First pass at functions + tests

This commit is contained in:
Stephen Dolan
2012-08-21 18:14:13 +01:00
parent dd0d340eba
commit c128c2029e
15 changed files with 461 additions and 89 deletions

View File

@@ -14,11 +14,23 @@ struct inst {
uint16_t intval;
struct inst* target;
json_t* constant;
char* symbol;
} imm;
struct inst* var_binding;
int var_frame_idx;
// Binding
// An instruction requiring binding (for parameters/variables)
// is in one of three states:
// bound_by = NULL - Unbound free variable
// bound_by = self - This instruction binds a variable
// bound_by = other - Uses variable bound by other instruction
// The immediate field is generally not meaningful until instructions
// are bound, and even then only for instructions which bind.
struct inst* bound_by;
char* symbol;
block subfn;
// This instruction is compiled as part of which function?
// (only used during block_compile)
struct bytecode* compiled;
int bytecode_pos; // position just after this insn
};
@@ -28,15 +40,16 @@ static inst* inst_new(opcode op) {
i->next = i->prev = 0;
i->op = op;
i->bytecode_pos = -1;
i->var_binding = 0;
i->var_frame_idx = 0;
i->bound_by = 0;
i->symbol = 0;
i->subfn = gen_noop();
return i;
}
static void inst_free(struct inst* i) {
if (opcode_describe(i->op)->flags &
(OP_HAS_SYMBOL | OP_HAS_VARIABLE)) {
free(i->imm.symbol);
free(i->symbol);
if (opcode_describe(i->op)->flags & OP_HAS_BLOCK) {
block_free(i->subfn);
}
free(i);
}
@@ -89,25 +102,56 @@ void inst_set_target(block b, block target) {
block gen_op_var_unbound(opcode op, const char* name) {
assert(opcode_describe(op)->flags & OP_HAS_VARIABLE);
inst* i = inst_new(op);
i->imm.symbol = strdup(name);
i->symbol = strdup(name);
return inst_block(i);
}
block gen_op_var_bound(opcode op, block binder) {
assert(opcode_describe(op)->flags & OP_HAS_VARIABLE);
assert(binder.first);
assert(binder.first == binder.last);
block b = gen_op_var_unbound(op, binder.first->imm.symbol);
b.first->var_binding = binder.first;
block b = gen_op_var_unbound(op, binder.first->symbol);
b.first->bound_by = binder.first;
return b;
}
block gen_op_symbol(opcode op, const char* sym) {
assert(opcode_describe(op)->flags & OP_HAS_SYMBOL);
inst* i = inst_new(op);
i->imm.symbol = strdup(sym);
i->symbol = strdup(sym);
return inst_block(i);
}
block gen_op_block_defn(opcode op, const char* name, block block) {
assert(opcode_describe(op)->flags & OP_IS_CALL_PSEUDO);
assert(opcode_describe(op)->flags & OP_HAS_BLOCK);
inst* i = inst_new(op);
i->subfn = block;
i->symbol = strdup(name);
return inst_block(i);
}
block gen_op_block_unbound(opcode op, const char* name) {
assert(opcode_describe(op)->flags & OP_IS_CALL_PSEUDO);
inst* i = inst_new(op);
i->symbol = strdup(name);
return inst_block(i);
}
block gen_op_call(opcode op, block arglist) {
assert(opcode_describe(op)->flags & OP_HAS_VARIABLE_LENGTH_ARGLIST);
inst* i = inst_new(op);
int nargs = 0;
for (inst* curr = arglist.first; curr; curr = curr->next) {
assert(opcode_describe(curr->op)->flags & OP_IS_CALL_PSEUDO);
nargs++;
}
assert(nargs < 100); //FIXME
i->imm.intval = nargs;
return block_join(inst_block(i), arglist);
}
static void inst_join(inst* a, inst* b) {
assert(a && b);
assert(!a->next);
@@ -133,22 +177,31 @@ block block_join(block a, block b) {
return c;
}
block block_bind(block binder, block body) {
static void block_bind_subblock(block binder, block body, int bindflags) {
assert(binder.first);
assert(binder.first == binder.last);
assert(opcode_describe(binder.first->op)->flags & OP_HAS_VARIABLE);
assert(binder.first->imm.symbol);
assert(binder.first->var_binding == 0);
assert((opcode_describe(binder.first->op)->flags & bindflags) == bindflags);
assert(binder.first->symbol);
assert(binder.first->bound_by == 0 || binder.first->bound_by == binder.first);
binder.first->var_binding = binder.first;
binder.first->bound_by = binder.first;
for (inst* i = body.first; i; i = i->next) {
if (opcode_describe(i->op)->flags & OP_HAS_VARIABLE &&
i->var_binding == 0 &&
!strcmp(i->imm.symbol, binder.first->imm.symbol)) {
// bind this variable
i->var_binding = binder.first;
int flags = opcode_describe(i->op)->flags;
if ((flags & bindflags) == bindflags &&
i->bound_by == 0 &&
!strcmp(i->symbol, binder.first->symbol)) {
// bind this instruction
i->bound_by = binder.first;
}
if (flags & OP_HAS_BLOCK) {
block_bind_subblock(binder, i->subfn, bindflags);
}
}
}
block block_bind(block binder, block body, int bindflags) {
bindflags |= OP_HAS_BINDING;
block_bind_subblock(binder, body, bindflags);
return block_join(binder, body);
}
@@ -180,7 +233,7 @@ block gen_collect(block expr) {
block_append(&c, gen_op_simple(DUP));
block_append(&c, gen_op_const(LOADK, json_array()));
block array_var = block_bind(gen_op_var_unbound(STOREV, "collect"),
gen_noop());
gen_noop(), OP_HAS_VARIABLE);
block_append(&c, array_var);
block tail = {0};
@@ -204,64 +257,108 @@ block gen_else(block a, block b) {
assert(0);
}
static uint16_t nesting_level(struct bytecode* bc, inst* target) {
uint16_t level = 0;
assert(bc && target->compiled);
while (bc && target->compiled != bc) {
level++;
bc = bc->parent;
}
assert(bc && bc == target->compiled);
return level;
}
struct bytecode* block_compile(struct symbol_table* syms, block b) {
inst* curr = b.first;
static void compile(struct bytecode* bc, block b) {
int pos = 0;
int var_frame_idx = 0;
for (; curr; curr = curr->next) {
bc->nsubfunctions = 0;
for (inst* curr = b.first; curr; curr = curr->next) {
if (!curr->next) assert(curr == b.last);
pos += opcode_length(curr->op);
curr->bytecode_pos = pos;
if (opcode_describe(curr->op)->flags & OP_HAS_VARIABLE) {
assert(curr->var_binding && "unbound variable");
if (curr->var_binding == curr) {
curr->var_frame_idx = var_frame_idx++;
}
curr->compiled = bc;
int opflags = opcode_describe(curr->op)->flags;
if (opflags & OP_HAS_BINDING) {
assert(curr->bound_by && "unbound term");
}
if ((opflags & OP_HAS_VARIABLE) &&
curr->bound_by == curr) {
curr->imm.intval = var_frame_idx++;
}
if (opflags & OP_HAS_BLOCK) {
assert(curr->bound_by == curr);
curr->imm.intval = bc->nsubfunctions++;
}
}
struct bytecode* bc = malloc(sizeof(struct bytecode));
if (bc->nsubfunctions) {
bc->subfunctions = malloc(sizeof(struct bytecode*) * bc->nsubfunctions);
for (inst* curr = b.first; curr; curr = curr->next) {
if (!(opcode_describe(curr->op)->flags & OP_HAS_BLOCK))
continue;
struct bytecode* subfn = malloc(sizeof(struct bytecode));
bc->subfunctions[curr->imm.intval] = subfn;
subfn->globals = bc->globals;
subfn->parent = bc;
compile(subfn, curr->subfn);
}
} else {
bc->subfunctions = 0;
}
bc->codelen = pos;
uint16_t* code = malloc(sizeof(uint16_t) * bc->codelen);
bc->code = code;
int* stack_height = malloc(sizeof(int) * (bc->codelen + 1));
for (int i = 0; i<bc->codelen + 1; i++) stack_height[i] = -1;
pos = 0;
json_t* constant_pool = json_array();
int maxvar = -1;
int curr_stack_height = 1;
for (curr = b.first; curr; curr = curr->next) {
for (inst* curr = b.first; curr; curr = curr->next) {
if (curr->op == CLOSURE_CREATE) {
// CLOSURE_CREATE opcodes define closures for use later in the
// codestream. They generate no code.
// FIXME: make the above true :)
code[pos++] = DUP;
code[pos++] = POP;
continue;
}
const struct opcode_description* op = opcode_describe(curr->op);
if (curr_stack_height < op->stack_in) {
printf("Stack underflow at %04d\n", curr->bytecode_pos);
}
if (stack_height[curr->bytecode_pos] != -1 &&
stack_height[curr->bytecode_pos] != curr_stack_height) {
// FIXME: not sure this is right at all :(
printf("Inconsistent stack heights at %04d %s\n", curr->bytecode_pos, op->name);
}
curr_stack_height -= op->stack_in;
curr_stack_height += op->stack_out;
code[pos++] = curr->op;
int opflags = op->flags;
if (opflags & OP_HAS_CONSTANT) {
assert(!(op->flags & OP_IS_CALL_PSEUDO));
if (opflags & OP_HAS_VARIABLE_LENGTH_ARGLIST) {
int nargs = curr->imm.intval;
assert(nargs > 0);
code[pos++] = (uint16_t)nargs;
for (int i=0; i<nargs; i++) {
curr = curr->next;
assert(curr && opcode_describe(curr->op)->flags & OP_IS_CALL_PSEUDO);
code[pos++] = nesting_level(bc, curr->bound_by);
switch (curr->bound_by->op) {
default: assert(0 && "Unknown type of argument");
case CLOSURE_CREATE:
code[pos++] = curr->bound_by->imm.intval | ARG_NEWCLOSURE;
break;
}
}
} else if (opflags & OP_HAS_CONSTANT) {
code[pos++] = json_array_size(constant_pool);
json_array_append(constant_pool, curr->imm.constant);
} else if (opflags & OP_HAS_VARIABLE) {
uint16_t var = (uint16_t)curr->var_binding->var_frame_idx;
// no closing over variables yet
assert(curr->bound_by->compiled == bc);
uint16_t var = (uint16_t)curr->bound_by->imm.intval;
code[pos++] = var;
if (var > maxvar) maxvar = var;
} else if (opflags & OP_HAS_BRANCH) {
assert(curr->imm.target->bytecode_pos != -1);
assert(curr->imm.target->bytecode_pos > pos); // only forward branches
code[pos] = curr->imm.target->bytecode_pos - (pos + 1);
stack_height[curr->imm.target->bytecode_pos] = curr_stack_height;
pos++;
} else if (opflags & OP_HAS_CFUNC) {
assert(curr->imm.symbol);
assert(curr->symbol);
int found = 0;
for (int i=0; i<syms->ncfunctions; i++) {
if (!strcmp(curr->imm.symbol, syms->cfunctions[i].name)) {
for (int i=0; i<bc->globals->ncfunctions; i++) {
if (!strcmp(curr->symbol, bc->globals->cfunctions[i].name)) {
code[pos++] = i;
found = 1;
break;
@@ -272,11 +369,16 @@ struct bytecode* block_compile(struct symbol_table* syms, block b) {
code[pos++] = curr->imm.intval;
}
}
free(stack_height);
bc->constants = constant_pool;
bc->nlocals = maxvar + 2; // FIXME: frames of size zero?
bc->nclosures = 0;
}
struct bytecode* block_compile(struct symbol_table* syms, block b) {
struct bytecode* bc = malloc(sizeof(struct bytecode));
bc->parent = 0;
bc->globals = syms;
compile(bc, b);
return bc;
}