aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/module.c
diff options
context:
space:
mode:
Diffstat (limited to 'prototypes/connections/module.c')
-rw-r--r--prototypes/connections/module.c156
1 files changed, 128 insertions, 28 deletions
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c
index 2e5375c..4b5d15d 100644
--- a/prototypes/connections/module.c
+++ b/prototypes/connections/module.c
@@ -12,9 +12,9 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES =
enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7 };
enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA };
/* Where an opcode may appear. */
-enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16 };
+enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32 };
enum { READ = IN_VIEW | IN_HANDLER };
-static const unsigned char magic[8] = {'P','U','R','E','M','O','D','4'};
+static const unsigned char magic[8] = {'P','U','R','E','M','O','D','5'};
/* Operand types popped (bottom to top), the type pushed, and where allowed. */
static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP_COUNT] = {
@@ -39,8 +39,8 @@ static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP
[PURE_OP_MAX] = {2, {T_NUM, T_NUM}, T_NUM, READ},
[PURE_OP_CLAMP] = {3, {T_NUM, T_NUM, T_NUM}, T_NUM, READ},
[PURE_OP_STORE] = {1, {T_NUM}, 0, IN_HANDLER},
- [PURE_OP_JZ] = {1, {T_NUM}, 0, IN_HANDLER},
- [PURE_OP_JMP] = {0, {0}, 0, IN_HANDLER},
+ [PURE_OP_JZ] = {1, {T_NUM}, 0, READ},
+ [PURE_OP_JMP] = {0, {0}, 0, READ},
[PURE_OP_FIELD] = {0, {0}, T_FIELD, IN_MESSAGE},
[PURE_OP_DOC] = {0, {0}, T_BYTES, IN_KEYED},
[PURE_OP_DOC_SIZE] = {0, {0}, T_NUM, IN_KEYED},
@@ -81,6 +81,7 @@ static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP
[PURE_OP_INDEX_FIRST] = {0, {0}, T_INDEX, READ},
[PURE_OP_INDEX_LAST] = {0, {0}, T_INDEX, READ},
[PURE_OP_INDEX_NUM] = {1, {T_INDEX}, T_NUM, READ},
+ [PURE_OP_ROW] = {0, {0}, T_INDEX, IN_ROW},
};
static uint32_t read16(const unsigned char *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8; }
@@ -151,7 +152,7 @@ static bool uses_arg(uint8_t op) {
case PURE_OP_TEXT_LINE: case PURE_OP_TEXT_SET: case PURE_OP_CUR_COUNT: case PURE_OP_LIST_COUNT:
case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: case PURE_OP_ITEM_GET:
case PURE_OP_ITEM_SET: case PURE_OP_INDEX_LOAD: case PURE_OP_INDEX_STORE: case PURE_OP_INDEX_FIRST:
- case PURE_OP_INDEX_LAST:
+ case PURE_OP_INDEX_LAST: case PURE_OP_ROW:
return true;
default: return false;
}
@@ -170,35 +171,62 @@ static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t fie
return m->record_field_text[m->record[m->state_ref[list]].first + field] ? T_TEXT : T_NUM;
}
+/* The stack a branch brings to its target, while the target is ahead. */
+typedef struct { uint32_t target; int height; unsigned char types[PURE_MODULE_STACK], owners[PURE_MODULE_STACK]; } Join;
+
+static bool same_stack(const Join *join, int height, const unsigned char *types, const unsigned char *owners) {
+ return join->height == height && !memcmp(join->types, types, (size_t)height) &&
+ !memcmp(join->owners, owners, (size_t)height);
+}
+
/* Types and heights are checked per instruction; a cursor's type records
* which text it belongs to and an index's type which list. Branches go
- * forward within the block and every join happens with an empty operand
- * stack, so control flow never merges typed values. Handlers end empty,
- * views with one value of their type. */
-static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_t event,
+ * forward within the block. Every path into a join brings the same stack
+ * (types and owners), so a value may cross a join, as in a conditional
+ * expression; at most 8 joins with a non-empty stack are ahead at once.
+ * Handlers end empty, views with one value of their type. A column (row
+ * is its list) may read the row it is evaluated for. */
+static bool verify_block(const PureModule *m, PureBlock block, int view, int row, uint32_t event,
char *error, size_t size, unsigned index) {
if (block.start > m->code_count || block.length > m->code_count - block.start)
return reject(error, size, "code range outside module", index);
if (view >= 0 && !block.length) return reject(error, size, "empty view", index);
int input = view >= 0 ? -1 : event_input(event);
const PureInputShape *shape = input >= 0 ? &m->input[input] : NULL;
- unsigned where = view >= 0 ? IN_VIEW : IN_HANDLER;
+ unsigned where = view >= 0 ? IN_VIEW | (row >= 0 ? IN_ROW : 0) : IN_HANDLER;
if (shape && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE | (shape->keyed ? IN_KEYED : 0);
if (shape && shape->kind == PURE_KIND_TEXT) where |= IN_TEXT;
unsigned char types[PURE_MODULE_STACK], owners[PURE_MODULE_STACK], target[PURE_MODULE_CODE + 1] = {0};
+ Join joins[PURE_MODULE_JOINS];
+ unsigned open = 0;
int height = 0;
bool reachable = true;
for (uint32_t i = 0; i <= block.length; ++i) {
uint32_t pc = block.start + i;
- if (!reachable && target[i]) { reachable = true; height = 0; }
- else if (target[i] && height) return reject(error, size, "join with a non-empty stack", pc);
+ /* Branches here brought an empty stack (target) or the stack of a join. */
+ unsigned join = open;
+ for (unsigned k = 0; k < open; ++k) if (joins[k].target == pc) join = k;
+ if (join < open) {
+ if (target[i] || (reachable && !same_stack(&joins[join], height, types, owners)))
+ return reject(error, size, "join with different stacks", pc);
+ height = joins[join].height;
+ memcpy(types, joins[join].types, (size_t)height);
+ memcpy(owners, joins[join].owners, (size_t)height);
+ joins[join] = joins[--open];
+ reachable = true;
+ } else if (target[i]) {
+ if (reachable && height) return reject(error, size, "join with different stacks", pc);
+ height = 0;
+ reachable = true;
+ }
if (i == block.length) break;
if (!reachable) return reject(error, size, "unreachable instruction", pc);
const PureOp *op = &m->code[pc];
if (op->op >= PURE_OP_COUNT) return reject(error, size, "unknown opcode", pc);
unsigned allowed = signature[op->op].where;
if (!(allowed & where)) {
- if (view >= 0) return reject(error, size, "view with an effect or branch", pc);
+ if (allowed & IN_ROW) return reject(error, size, "row outside a column", pc);
+ if (view >= 0) return reject(error, size, "view with an effect", pc);
if (allowed & IN_KEYED) return reject(error, size, "document operation outside a keyed message handler", pc);
if (allowed & IN_TEXT) return reject(error, size, "text input outside a text handler", pc);
return reject(error, size, "message operation outside a message handler", pc);
@@ -228,6 +256,7 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_
item_field(arg) < m->record[m->state_ref[list_of(arg)]].count;
break;
case PURE_OP_INDEX_LOAD: case PURE_OP_INDEX_STORE: known = state_of(m, arg, PURE_STATE_INDEX); break;
+ case PURE_OP_ROW: known = (int)arg == row; break;
default: break;
}
if (!known) return reject(error, size, "operand names no such state, field, constant or unit", pc);
@@ -275,7 +304,7 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_
if (height == PURE_MODULE_STACK) return reject(error, size, "stack overflow", pc);
owners[height] = (unsigned char)(op->op == PURE_OP_CUR_LOAD || op->op == PURE_OP_INDEX_LOAD ? m->state_ref[arg] :
op->op == PURE_OP_CUR_START || op->op == PURE_OP_CUR_END ||
- op->op == PURE_OP_INDEX_FIRST || op->op == PURE_OP_INDEX_LAST ? arg :
+ op->op == PURE_OP_INDEX_FIRST || op->op == PURE_OP_INDEX_LAST || op->op == PURE_OP_ROW ? arg :
op->op == PURE_OP_INDEX_MOVE ? index_owner :
owner >= 0 ? (unsigned)owner : 0);
types[height++] = out;
@@ -283,8 +312,20 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_
if (op->op == PURE_OP_JZ || op->op == PURE_OP_JMP) {
if (arg <= pc || arg > block.start + block.length)
return reject(error, size, "branch not forward within its block", pc);
- if (height) return reject(error, size, "branch with a non-empty stack", pc);
- target[arg - block.start] = 1;
+ unsigned join = open;
+ for (unsigned k = 0; k < open; ++k) if (joins[k].target == arg) join = k;
+ if (!height) {
+ if (join < open) return reject(error, size, "join with different stacks", pc);
+ target[arg - block.start] = 1;
+ } else if (join < open) {
+ if (!same_stack(&joins[join], height, types, owners)) return reject(error, size, "join with different stacks", pc);
+ } else {
+ if (target[arg - block.start]) return reject(error, size, "join with different stacks", pc);
+ if (open == PURE_MODULE_JOINS) return reject(error, size, "more than 8 joins ahead", pc);
+ joins[open] = (Join){.target = arg, .height = height};
+ memcpy(joins[open].types, types, (size_t)height);
+ memcpy(joins[open++].owners, owners, (size_t)height);
+ }
if (op->op == PURE_OP_JMP) reachable = false;
}
}
@@ -446,12 +487,22 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out,
(out->state_type[i] == PURE_STATE_LIST && out->state_ref[i] >= out->records) ||
(out->state_type[i] == PURE_STATE_INDEX && !state_of(out, out->state_ref[i], PURE_STATE_LIST)))
return reject(error, error_size, "state reference", i), PURE_INVALID;
+ /* A table names its list in place of code; its columns follow it. */
for (uint32_t i = 0; i < out->views; ++i, at += VIEW_BYTES) {
if (!name_at(text, strings, read32(at), true, out->view_names[i]))
return reject(error, error_size, "view name", i), PURE_INVALID;
- if (read32(at + 4) > PURE_VIEW_TEXT) return reject(error, error_size, "view type", i), PURE_INVALID;
- out->view_type[i] = (uint8_t)read32(at + 4);
+ out->view_type[i] = at[4];
+ out->view_table[i] = (uint16_t)read16(at + 6);
out->view_code[i] = (PureBlock){read32(at + 8), read32(at + 12)};
+ uint32_t table = out->view_table[i];
+ if (at[4] > PURE_VIEW_TABLE || at[5] || (table && (table > i || out->view_type[table - 1] != PURE_VIEW_TABLE ||
+ at[4] == PURE_VIEW_TABLE)))
+ return reject(error, error_size, "view type", i), PURE_INVALID;
+ if (at[4] == PURE_VIEW_TABLE) {
+ if (!state_of(out, out->view_code[i].start, PURE_STATE_LIST) || out->view_code[i].length)
+ return reject(error, error_size, "table list", i), PURE_INVALID;
+ out->view_list[i] = (uint16_t)out->view_code[i].start;
+ } else if (table) out->view_list[i] = out->view_list[table - 1];
}
for (uint32_t i = 0; i < out->handlers; ++i, at += HANDLER_BYTES) {
uint32_t event = read32(at);
@@ -472,14 +523,22 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out,
for (uint32_t i = 0; i < out->records; ++i) names[count++] = out->record_names[i];
for (uint32_t i = 0; i < out->collections; ++i) names[count++] = out->collection_names[i];
for (uint32_t i = 0; i < out->inputs; ++i) names[count++] = out->input_names[i];
- for (uint32_t i = 0; i < out->views; ++i) names[count++] = out->view_names[i];
+ for (uint32_t i = 0; i < out->views; ++i) if (!out->view_table[i]) names[count++] = out->view_names[i];
for (size_t i = 0; i < count; ++i)
for (size_t k = 0; k < i; ++k)
if (!strcmp(names[i], names[k])) return reject(error, error_size, "duplicate name", (unsigned)i), PURE_INVALID;
+ /* Column names are unique within their table. */
for (uint32_t i = 0; i < out->views; ++i)
- if (!verify_block(out, out->view_code[i], out->view_type[i], 0, error, error_size, i)) return PURE_INVALID;
+ for (uint32_t k = 0; k < i; ++k)
+ if (out->view_table[i] && out->view_table[k] == out->view_table[i] &&
+ !strcmp(out->view_names[i], out->view_names[k]))
+ return reject(error, error_size, "duplicate column", i), PURE_INVALID;
+ for (uint32_t i = 0; i < out->views; ++i)
+ if (out->view_type[i] != PURE_VIEW_TABLE &&
+ !verify_block(out, out->view_code[i], out->view_type[i], out->view_table[i] ? out->view_list[i] : -1, 0,
+ error, error_size, i)) return PURE_INVALID;
for (uint32_t i = 0; i < out->handlers; ++i)
- if (!verify_block(out, out->handler[i].code, -1, out->handler[i].event, error, error_size, i))
+ if (!verify_block(out, out->handler[i].code, -1, -1, out->handler[i].event, error, error_size, i))
return PURE_INVALID;
return PURE_OK;
}
@@ -492,7 +551,17 @@ static int find(const char (*names)[PURE_MODULE_NAME], unsigned count, const cha
int pure_module_state(const PureModule *m, const char *name) { return find(m->state_names, m->states, name); }
int pure_module_input(const PureModule *m, const char *name) { return find(m->input_names, m->inputs, name); }
-int pure_module_view(const PureModule *m, const char *name) { return find(m->view_names, m->views, name); }
+int pure_module_view(const PureModule *m, const char *name) {
+ for (uint32_t i = 0; i < m->views; ++i)
+ if (!m->view_table[i] && !strcmp(m->view_names[i], name)) return (int)i;
+ return -1;
+}
+
+int pure_module_column(const PureModule *m, uint32_t table, const char *name) {
+ for (uint32_t i = 0; i < m->views; ++i)
+ if (m->view_table[i] == table + 1 && !strcmp(m->view_names[i], name)) return (int)i;
+ return -1;
+}
/* ---- Execution ------------------------------------------------------ */
@@ -506,6 +575,7 @@ typedef struct {
const PureInputShape *shape; /* message, or NULL */
PureArena *arena;
double dt;
+ size_t row; /* the row a column is evaluated for */
bool modified, exists;
} Event;
@@ -917,6 +987,11 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even
continue;
}
case PURE_OP_INDEX_NUM: ++pc; continue; /* the position itself */
+ case PURE_OP_ROW:
+ stack[sp].num = (double)ev->row;
+ stack[sp++].bytes.owner = (uint16_t)op->arg;
+ ++pc;
+ continue;
default: break;
}
b = stack[--sp].num;
@@ -1133,11 +1208,11 @@ PureResult pure_app_reply(const PureApp *app, bool (*visit)(void *, PureSpan), v
}
/* A view that fails reports it by its result; the fault stays the last event's. */
-static PureResult evaluate(PureApp *app, uint32_t view, Slot *out) {
+static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) {
double values[PURE_MODULE_STATES];
memcpy(values, app->values, sizeof(values));
app->view_arena.used = 0;
- Event ev = {.app = app, .arena = &app->view_arena};
+ Event ev = {.app = app, .arena = &app->view_arena, .row = row};
PureFault fault = app->fault;
uint32_t fault_pc = app->fault_pc;
PureResult result = run(app->module, values, app->module->view_code[view], &ev, out);
@@ -1146,18 +1221,43 @@ static PureResult evaluate(PureApp *app, uint32_t view, Slot *out) {
return result;
}
+/* A view outside a table (column false), or a column and a row of its table. */
+static PureResult view_of(PureApp *app, uint32_t view, bool column, size_t row, uint8_t type, Slot *slot) {
+ const PureModule *m = app->module;
+ if (view >= m->views || m->view_type[view] != type || (m->view_table[view] != 0) != column) return PURE_INVALID;
+ if (column && row >= pure_list_count(app->lists[m->view_list[view]])) return PURE_NOT_FOUND;
+ return evaluate(app, view, row, slot);
+}
+
bool pure_app_view(PureApp *app, uint32_t view, double *out) {
- if (view >= app->module->views || app->module->view_type[view] != PURE_VIEW_NUM) return false;
Slot slot;
- if (evaluate(app, view, &slot) != PURE_OK) return false;
+ if (view_of(app, view, false, 0, PURE_VIEW_NUM, &slot) != PURE_OK) return false;
*out = slot.num;
return true;
}
PureResult pure_app_view_text(PureApp *app, uint32_t view, bool (*visit)(void *, PureSpan), void *context) {
- if (view >= app->module->views || app->module->view_type[view] != PURE_VIEW_TEXT) return PURE_INVALID;
Slot slot;
- PureResult result = evaluate(app, view, &slot);
+ PureResult result = view_of(app, view, false, 0, PURE_VIEW_TEXT, &slot);
+ return result == PURE_OK ? visit_value(app, &app->view_arena, slot.bytes, visit, context) : result;
+}
+
+size_t pure_app_rows(const PureApp *app, uint32_t table) {
+ const PureModule *m = app->module;
+ if (table >= m->views || m->view_type[table] != PURE_VIEW_TABLE) return 0;
+ return pure_list_count(app->lists[m->view_list[table]]);
+}
+
+bool pure_app_cell(PureApp *app, uint32_t column, size_t row, double *out) {
+ Slot slot;
+ if (view_of(app, column, true, row, PURE_VIEW_NUM, &slot) != PURE_OK) return false;
+ *out = slot.num;
+ return true;
+}
+
+PureResult pure_app_cell_text(PureApp *app, uint32_t column, size_t row, bool (*visit)(void *, PureSpan), void *context) {
+ Slot slot;
+ PureResult result = view_of(app, column, true, row, PURE_VIEW_TEXT, &slot);
return result == PURE_OK ? visit_value(app, &app->view_arena, slot.bytes, visit, context) : result;
}