aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/module.c178
-rw-r--r--core/module.h27
-rw-r--r--core/test-module.c139
3 files changed, 289 insertions, 55 deletions
diff --git a/core/module.c b/core/module.c
index 20b1748..643d11c 100644
--- a/core/module.c
+++ b/core/module.c
@@ -12,9 +12,10 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES =
/* T_FIELD and T_ITEM stand for the type of the message or record field
* named, T_KEY for the type of the named keyed list's key. */
enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7, T_KEY = 8 };
-enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA };
+enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA, FROM_ARGUMENTS };
/* Where an opcode may appear. */
-enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32, IN_OUTER = 64 };
+/* IN_ARGUMENTS: fields read where nothing is done with them (a view's arguments, a message's fields in a fold). */
+enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32, IN_OUTER = 64, IN_ARGUMENTS = 128 };
enum { READ = IN_VIEW | IN_HANDLER };
/* What an argument names: nothing (it is 0), a state of one type (A_STATE +
* the type), a list and one of its record's fields, a message field, the
@@ -51,7 +52,7 @@ static const struct { unsigned char pops, in[3], out, where, arg, of; } signatur
[PURE_OP_STORE] = {1, {T_NUM}, 0, IN_HANDLER, STATE(NUM), O_ANY},
[PURE_OP_JZ] = {1, {T_NUM}, 0, READ, A_TARGET, O_ANY},
[PURE_OP_JMP] = {0, {0}, 0, READ, A_TARGET, O_ANY},
- [PURE_OP_FIELD] = {0, {0}, T_FIELD, IN_MESSAGE, A_FIELD, O_ANY},
+ [PURE_OP_FIELD] = {0, {0}, T_FIELD, IN_MESSAGE | IN_ARGUMENTS, A_FIELD, O_ANY},
[PURE_OP_DOC] = {0, {0}, T_BYTES, IN_KEYED, A_COLLECTION, O_ANY},
[PURE_OP_DOC_SIZE] = {0, {0}, T_NUM, IN_KEYED, A_COLLECTION, O_ANY},
[PURE_OP_LEN] = {1, {T_BYTES}, T_NUM, IN_HANDLER, A_NONE, O_ANY},
@@ -235,12 +236,14 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
#define REFUSE(why) do { *at = pc; return why; } while (0)
bool handler = view < 0 || view == WALK, folding = view == FOLD_ONE || view == FOLD_TWO;
if (!handler && !block.length) return "empty view";
- int input = handler ? event_input(event) : -1;
+ /* A handler's message or text; a view's arguments, or the message of the handler a fold is in. */
+ int input = event_input(event);
const PureInputShape *shape = input >= 0 ? &m->input[input] : NULL;
unsigned where = !handler ? IN_VIEW | (row >= 0 ? IN_ROW : 0) | (folding && outer >= 0 ? IN_OUTER : 0)
: IN_HANDLER | (view == WALK ? IN_ROW : 0);
- 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;
+ if (shape && handler && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE | (shape->keyed ? IN_KEYED : 0);
+ if (shape && handler && shape->kind == PURE_KIND_TEXT) where |= IN_TEXT;
+ if (shape && !handler && (shape->kind == PURE_KIND_MESSAGE || shape->kind == PURE_KIND_ARGUMENTS)) where |= IN_ARGUMENTS;
unsigned char types[PURE_MODULE_STACK], owners[PURE_MODULE_STACK];
Join joins[PURE_MODULE_JOINS];
unsigned open = 0;
@@ -316,7 +319,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
event, &inner)
: verify_block(m, (PureBlock){pc + 1, skip},
how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE : FOLD_TWO,
- (int)(arg >> 8 & 0xFF), row, 0, &inner);
+ (int)(arg >> 8 & 0xFF), row, event, &inner);
if (why) { *at = inner; return why; }
for (uint32_t k = i + 1; k <= i + skip; ++k) {
const char *into = kind == A_FOLD ? "branch into a fold" : "branch into an each";
@@ -415,8 +418,12 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block,
/* Whether an instruction has a form that works on the items of a run at
* once: numbers and indexes, with no branch and no text. */
-PURE_COLD static bool has_lanes(const PureModule *m, const PureOp *op) {
+PURE_COLD static bool has_lanes(const PureModule *m, const PureOp *op, int input) {
switch (op->op) {
+ case PURE_OP_FIELD: { /* a number the message or the arguments give, the same for every item */
+ uint8_t type = input >= 0 ? m->field[m->input[input].field_start + op->arg].type : 0;
+ return type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER;
+ }
case PURE_OP_CONST: case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL: case PURE_OP_DIV: case PURE_OP_NEG:
case PURE_OP_LT: case PURE_OP_LE: case PURE_OP_GT: case PURE_OP_GE: case PURE_OP_EQ: case PURE_OP_NE:
case PURE_OP_AND: case PURE_OP_OR: case PURE_OP_NOT: case PURE_OP_MIN: case PURE_OP_MAX: case PURE_OP_CLAMP:
@@ -430,19 +437,19 @@ PURE_COLD static bool has_lanes(const PureModule *m, const PureOp *op) {
}
}
-PURE_COLD static bool block_has_lanes(const PureModule *m, PureBlock block) {
+PURE_COLD static bool block_has_lanes(const PureModule *m, PureBlock block, int input) {
for (uint32_t pc = block.start; pc < block.start + block.length; ++pc)
- if (!has_lanes(m, &m->code[pc])) return false;
+ if (!has_lanes(m, &m->code[pc], input)) return false;
return true;
}
/* A fold in a verified block whose own block has lane forms throughout runs
* a leaf of its list at a time. */
-PURE_COLD static void plan_folds(PureModule *m, PureBlock block) {
+PURE_COLD static void plan_folds(PureModule *m, PureBlock block, int input) {
for (uint32_t pc = block.start; pc < block.start + block.length; ++pc) {
PureOp *op = &m->code[pc];
if (op->op != PURE_OP_FOLD) continue;
- op->plan = block_has_lanes(m, (PureBlock){pc + 1, op->arg >> 16});
+ op->plan = block_has_lanes(m, (PureBlock){pc + 1, op->arg >> 16}, input);
pc += op->arg >> 16;
}
}
@@ -457,7 +464,7 @@ typedef struct { uint32_t first; int kid[3]; bool item, own; } Node;
PURE_COLD static unsigned arity(uint8_t op) {
switch (op) {
case PURE_OP_CONST: case PURE_OP_LOAD: case PURE_OP_INDEX_LOAD: case PURE_OP_ROW: case PURE_OP_OUTER_ROW:
- case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST:
+ case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_FIELD:
return 0;
case PURE_OP_ITEM_GET: case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: case PURE_OP_NEG: case PURE_OP_NOT:
case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM:
@@ -490,7 +497,7 @@ PURE_COLD static bool block_tree(const PureModule *m, PureBlock block, int list,
node->first = block.start + i;
node->item = op->op == PURE_OP_ROW;
node->own = op->op != PURE_OP_LOAD && op->op != PURE_OP_INDEX_LOAD && op->op != PURE_OP_OUTER_ROW &&
- (read < 0 || read == list);
+ op->op != PURE_OP_FIELD && (read < 0 || read == list);
for (unsigned k = 0; k < 3; ++k) node->kid[k] = -1;
for (unsigned k = 0; k < pops; ++k) {
int kid = stack[sp - pops + k];
@@ -690,7 +697,9 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
shape->keyed = at[5];
shape->field_start = (uint16_t)read16(at + 12);
shape->field_count = (uint16_t)read16(at + 14);
- if (!name_at(text, strings, read32(at), NAME_IDENTIFIER, out->input_names[i])) REFUSE("input name", i);
+ /* Arguments bear their view's name (view.args); other inputs are identifiers. */
+ if (!name_at(text, strings, read32(at), shape->kind == PURE_KIND_ARGUMENTS ? NAME_PATH : NAME_IDENTIFIER,
+ out->input_names[i])) REFUSE("input name", i);
if (read16(at + 6) || at[5] > 1) REFUSE("input flags", i);
if (shape->kind == PURE_KIND_KEY) {
if (shape->keyed || shape->field_start || shape->field_count ||
@@ -712,9 +721,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
shape->period > PURE_TIMER_LONGEST) REFUSE("timer input", i);
continue;
}
- if ((shape->kind != PURE_KIND_MESSAGE && shape->kind != PURE_KIND_OUTPUT) || read32(at + 8) ||
- (shape->kind == PURE_KIND_OUTPUT && shape->keyed) || shape->field_start != next_field ||
+ bool arguments = shape->kind == PURE_KIND_ARGUMENTS;
+ if ((shape->kind != PURE_KIND_MESSAGE && shape->kind != PURE_KIND_OUTPUT && !arguments) || (!arguments && read32(at + 8)) ||
+ ((shape->kind == PURE_KIND_OUTPUT || arguments) && shape->keyed) || shape->field_start != next_field ||
shape->field_count > out->fields - next_field) REFUSE("message shape", i);
+ shape->view = arguments ? read32(at + 8) : 0;
/* The prefix holds the fixed fields and the variable fields' lengths
* in order, but the last variable field's: it takes the rest. */
uint32_t last = UINT32_MAX;
@@ -735,7 +746,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if (field->type == PURE_FIELD_ROWS) {
char path[2 * PURE_MODULE_NAME + 2];
snprintf(path, sizeof(path), "%s.%s", out->input_names[i], field->name);
- if (shape->kind == PURE_KIND_OUTPUT || field->rows >= out->states ||
+ if (shape->kind != PURE_KIND_MESSAGE || field->rows >= out->states ||
out->state_type[field->rows] != PURE_STATE_LIST || !(out->state_flags[field->rows] & PURE_LIST_ROWS >> 8) ||
strcmp(out->state_names[field->rows], path))
REFUSE("rows field", next_field + f);
@@ -813,12 +824,25 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->view_list[i] = (uint16_t)list;
} else if (table) out->view_list[i] = out->view_list[table - 1];
}
+ /* Arguments are a view's (not a column's: a table's columns and filter
+ * read the table's), one set a view, bearing its name. */
+ for (uint32_t i = 0; i < out->inputs; ++i) {
+ const PureInputShape *shape = &out->input[i];
+ if (shape->kind != PURE_KIND_ARGUMENTS) continue;
+ char path[PURE_MODULE_NAME + 8];
+ if (shape->view >= out->views || out->view_table[shape->view] || out->view_arguments[shape->view]) REFUSE("arguments", i);
+ snprintf(path, sizeof(path), "%s.args", out->view_names[shape->view]);
+ if (strcmp(path, out->input_names[i])) REFUSE("arguments", i);
+ out->view_arguments[shape->view] = (uint8_t)(i + 1);
+ }
+ for (uint32_t i = 0; i < out->views; ++i)
+ if (out->view_table[i]) out->view_arguments[i] = out->view_arguments[out->view_table[i] - 1];
for (uint32_t i = 0; i < out->handlers; ++i, at += HANDLER_BYTES) {
uint32_t event = read32(at);
out->handler[i] = (PureHandler){event, {read32(at + 4), read32(at + 8)}};
int input = event_input(event);
if (event > 2u * out->inputs || (input >= 0 && out->input[input].kind != PURE_KIND_KEY && !(event & 1)) ||
- (input >= 0 && out->input[input].kind == PURE_KIND_OUTPUT))
+ (input >= 0 && (out->input[input].kind == PURE_KIND_OUTPUT || out->input[input].kind == PURE_KIND_ARGUMENTS)))
REFUSE("handler event", i);
}
for (uint32_t i = 0; i < out->code_count; ++i, at += OP_BYTES) {
@@ -845,18 +869,20 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
for (uint32_t i = 0; i < out->views; ++i) {
bad = i;
bool table = out->view_type[i] == PURE_VIEW_TABLE;
+ uint32_t event = out->view_arguments[i] ? 2u * out->view_arguments[i] - 1u : 0; /* the arguments' input, as an arrival */
if ((!table || out->view_code[i].length) &&
(why = verify_block(out, out->view_code[i], table ? FILTER : out->view_type[i],
- table || out->view_table[i] ? out->view_list[i] : -1, -1, 0, &bad))) goto refused;
+ table || out->view_table[i] ? out->view_list[i] : -1, -1, event, &bad))) goto refused;
}
for (uint32_t i = 0; i < out->handlers; ++i) {
bad = i;
if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event, &bad))) goto refused;
}
for (uint32_t i = 0; i < out->views; ++i) {
- plan_folds(out, out->view_code[i]);
+ int given = (int)out->view_arguments[i] - 1;
+ plan_folds(out, out->view_code[i], given);
out->view_plan[i] = out->view_type[i] == PURE_VIEW_TABLE && out->view_code[i].length &&
- block_has_lanes(out, out->view_code[i]);
+ block_has_lanes(out, out->view_code[i], given);
/* A fold in a column runs for every row: one pass may answer them all. */
for (uint32_t pc = out->view_code[i].start; out->view_table[i] && pc < out->view_code[i].start + out->view_code[i].length; ++pc)
if (out->code[pc].op == PURE_OP_FOLD) {
@@ -864,7 +890,10 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
pc += out->code[pc].arg >> 16;
}
}
- for (uint32_t i = 0; i < out->handlers; ++i) plan_folds(out, out->handler[i].code);
+ for (uint32_t i = 0; i < out->handlers; ++i) {
+ int input = event_input(out->handler[i].event);
+ plan_folds(out, out->handler[i].code, input >= 0 && out->input[input].kind == PURE_KIND_MESSAGE ? input : -1);
+ }
return PURE_OK;
refused:
if (error_size) snprintf(error, error_size, "%s (%u)", why, bad);
@@ -925,6 +954,7 @@ typedef struct {
size_t row; /* the row a column is evaluated for (in a fold, the fold's item) */
size_t outer; /* in a fold, the row of the column it runs in */
Walk *walk; /* the walk the code runs in, or NULL */
+ bool arguments; /* FIELD reads the arguments given for a view, not the message */
bool modified, exists;
uint32_t (*spans)[2]; /* a message's variable fields: start and size in the payload */
} Event;
@@ -957,6 +987,7 @@ static PureResult visit_value(const PureApp *app, const PureArena *arena, PureBy
const unsigned char *direct = NULL;
switch (v.source) {
case FROM_PAYLOAD: direct = app->payload.data + v.offset; break;
+ case FROM_ARGUMENTS: direct = app->arguments.data + v.offset; break;
case FROM_COPY: direct = app->copied + v.offset; break;
case FROM_CONSTANT: direct = app->module->constant_data + v.offset; break;
case FROM_ARENA: direct = arena->bytes + v.offset; break;
@@ -987,8 +1018,8 @@ static PureResult append_value(const PureApp *app, PureArena *arena, PureBytes v
/* A contiguous span of a value, copying into the arena when it is spread out. */
static PureResult contiguous(const PureApp *app, PureArena *arena, PureBytes v, PureSpan *out) {
- if (v.source == FROM_PAYLOAD || v.source == FROM_COPY || v.source == FROM_CONSTANT) {
- const unsigned char *base = v.source == FROM_PAYLOAD ? app->payload.data :
+ if (v.source == FROM_PAYLOAD || v.source == FROM_COPY || v.source == FROM_CONSTANT || v.source == FROM_ARGUMENTS) {
+ const unsigned char *base = v.source == FROM_PAYLOAD ? app->payload.data : v.source == FROM_ARGUMENTS ? app->arguments.data :
v.source == FROM_COPY ? app->copied : app->module->constant_data;
*out = (PureSpan){v.length ? base + v.offset : NULL, (size_t)v.length};
return PURE_OK;
@@ -1244,6 +1275,13 @@ static bool run_lanes(const PureModule *m, const double *values, PureBlock block
switch (op->op) {
case PURE_OP_CONST: stack[sp++] = (Lanes){NULL, op->value, false, 0}; continue;
case PURE_OP_LOAD: stack[sp++] = (Lanes){NULL, values[arg], false, 0}; continue;
+ case PURE_OP_FIELD: {
+ const PureField *field = &m->field[ev->shape->field_start + arg];
+ const unsigned char *given = ev->arguments ? app->arguments.data : app->payload.data;
+ double value = field->type == PURE_FIELD_U32 ? read32(given + field->offset) : read_double(given + field->offset);
+ stack[sp++] = (Lanes){NULL, value, false, 0};
+ continue;
+ }
case PURE_OP_INDEX_LOAD: stack[sp++] = (Lanes){NULL, values[arg], false, m->state_ref[arg]}; continue;
case PURE_OP_ROW: stack[sp++] = (Lanes){NULL, 0, true, (uint16_t)arg}; continue;
case PURE_OP_OUTER_ROW: stack[sp++] = (Lanes){NULL, (double)ev->outer, false, (uint16_t)arg}; continue;
@@ -1706,9 +1744,11 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even
}
case PURE_OP_FIELD: {
const PureField *field = &m->field[ev->shape->field_start + arg];
- if (field->type == PURE_FIELD_U32) stack[sp++].num = read32(app->payload.data + field->offset);
- else if (field->type == PURE_FIELD_NUMBER) stack[sp++].num = read_double(app->payload.data + field->offset);
- else stack[sp++].bytes = (PureBytes){FROM_PAYLOAD, 0, {0, 0}, ev->spans[arg][0], ev->spans[arg][1]};
+ const unsigned char *given = ev->arguments ? app->arguments.data : app->payload.data;
+ if (field->type == PURE_FIELD_U32) stack[sp++].num = read32(given + field->offset);
+ else if (field->type == PURE_FIELD_NUMBER) stack[sp++].num = read_double(given + field->offset);
+ else stack[sp++].bytes = (PureBytes){ev->arguments ? FROM_ARGUMENTS : FROM_PAYLOAD, 0, {0, 0}, ev->spans[arg][0],
+ ev->spans[arg][1]};
continue;
}
case PURE_OP_DOC: case PURE_OP_DOC_SIZE: {
@@ -2412,6 +2452,50 @@ static PureResult keep_reply(PureApp *app) {
return PURE_OK;
}
+/* Where each variable field of a payload of shape is: where placed says
+ * (start and size in the payload), or, without it, one after another after
+ * the prefix. A length past the payload, a text that is not UTF-8 or a
+ * number that is not finite is its fault (else NONE). */
+static PureFault place_fields(const PureModule *m, const PureInputShape *shape, PureSpan payload, const uint32_t *placed,
+ uint32_t spans[][2]) {
+ if (payload.size < shape->prefix) return PURE_FAULT_LIMIT;
+ size_t at = shape->prefix;
+ for (uint32_t f = 0; f < shape->field_count; ++f) {
+ const PureField *field = &m->field[shape->field_start + f];
+ if (field->type == PURE_FIELD_NUMBER && !isfinite(read_double(payload.data + field->offset)))
+ return PURE_FAULT_NON_FINITE;
+ if (field->type != PURE_FIELD_BYTES && field->type != PURE_FIELD_TEXT && field->type != PURE_FIELD_ROWS) continue;
+ size_t start = placed ? placed[2 * f] : at;
+ if (start > payload.size) return PURE_FAULT_LIMIT;
+ size_t size = placed ? placed[2 * f + 1] : field->offset == PURE_FIELD_REST ? payload.size - at :
+ read32(payload.data + field->offset);
+ if (size > payload.size - start) return PURE_FAULT_LIMIT;
+ if (field->type == PURE_FIELD_TEXT && !pure_utf8_valid((PureSpan){size ? payload.data + start : NULL, size}))
+ return PURE_FAULT_ENCODING;
+ spans[f][0] = (uint32_t)start;
+ spans[f][1] = (uint32_t)size;
+ at = start + size;
+ }
+ return PURE_FAULT_NONE;
+}
+
+int pure_module_arguments(const PureModule *m, uint32_t view) {
+ return view < m->views && m->view_arguments[view] ? m->view_arguments[view] - 1 : -1;
+}
+
+PureResult pure_app_arguments(PureApp *app, uint32_t view, PureSpan payload, const uint32_t *placed) {
+ const PureModule *m = app->module;
+ int input = pure_module_arguments(m, view);
+ if (input < 0 || (payload.size && !payload.data)) return PURE_INVALID;
+ app->argued = 0;
+ ++app->arguments_given; /* rows a filter kept are for other arguments */
+ PureFault wrong = place_fields(m, &m->input[input], payload, placed, app->argument_spans);
+ if (wrong != PURE_FAULT_NONE) return wrong == PURE_FAULT_LIMIT ? PURE_LIMIT : PURE_INVALID;
+ app->arguments = payload;
+ app->argued = (unsigned)input + 1;
+ return PURE_OK;
+}
+
/* A message whose variable fields are where placed says (start and size in
* the payload), or, without it, one after another after the prefix. */
static PureResult message(PureApp *app, uint32_t input, const char *key, int64_t base, PureSpan payload,
@@ -2425,27 +2509,9 @@ static PureResult message(PureApp *app, uint32_t input, const char *key, int64_t
(payload.size && !payload.data) || (m->input[input].keyed ? !document_key(key) : key != NULL))
return PURE_INVALID;
const PureInputShape *shape = &m->input[input];
- if (payload.size < shape->prefix) return fault(app, PURE_FAULT_LIMIT, UINT32_MAX);
- /* Where each variable field is; a length past the payload, a text that
- * is not UTF-8 or a number that is not finite fails the message. */
uint32_t spans[PURE_MODULE_FIELDS][2];
- size_t at = shape->prefix;
- for (uint32_t f = 0; f < shape->field_count; ++f) {
- const PureField *field = &m->field[shape->field_start + f];
- if (field->type == PURE_FIELD_NUMBER && !isfinite(read_double(payload.data + field->offset)))
- return fault(app, PURE_FAULT_NON_FINITE, UINT32_MAX);
- if (field->type != PURE_FIELD_BYTES && field->type != PURE_FIELD_TEXT && field->type != PURE_FIELD_ROWS) continue;
- size_t start = placed ? placed[2 * f] : at;
- if (start > payload.size) return fault(app, PURE_FAULT_LIMIT, UINT32_MAX);
- size_t size = placed ? placed[2 * f + 1] : field->offset == PURE_FIELD_REST ? payload.size - at :
- read32(payload.data + field->offset);
- if (size > payload.size - start) return fault(app, PURE_FAULT_LIMIT, UINT32_MAX);
- if (field->type == PURE_FIELD_TEXT && !pure_utf8_valid((PureSpan){size ? payload.data + start : NULL, size}))
- return fault(app, PURE_FAULT_ENCODING, UINT32_MAX);
- spans[f][0] = (uint32_t)start;
- spans[f][1] = (uint32_t)size;
- at = start + size;
- }
+ PureFault wrong = place_fields(m, shape, payload, placed, spans);
+ if (wrong != PURE_FAULT_NONE) return fault(app, wrong, UINT32_MAX);
if (shape->keyed && m->collections) {
if (!app->data.open) return PURE_INVALID;
/* A key joins a burst once, so its revision moves once per save. */
@@ -2557,11 +2623,23 @@ 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. */
+/* The arguments a view reads, set in its event; false when it takes some and they were not given. */
+static bool argue(const PureApp *app, uint32_t view, Event *ev) {
+ unsigned given = app->module->view_arguments[view];
+ if (!given) return true;
+ if (app->argued != given) return false;
+ ev->shape = &app->module->input[given - 1];
+ ev->spans = (uint32_t (*)[2])app->argument_spans;
+ ev->arguments = true;
+ return true;
+}
+
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, .row = row};
+ if (!argue(app, view, &ev)) return PURE_INVALID;
PureFault fault = app->fault;
uint32_t fault_pc = app->fault_pc;
PureResult result = run(app->module, values, app->module->view_code[view], &ev, out);
@@ -2603,7 +2681,7 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) {
rows = &app->rows[app->row_tables++];
*rows = (struct PureRows){.table = table, .changes = UINT64_MAX};
}
- if (rows->changes == app->changes) return rows;
+ if (rows->changes == app->changes && (!m->view_arguments[table] || rows->given == app->arguments_given)) return rows;
rows->count = 0;
/* A leaf of the list at a time when the filter has lane forms; a leaf
* where an item would fault goes item by item, leaving that item out. */
@@ -2614,6 +2692,7 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) {
Event ev = {.app = app, .arena = &app->view_arena};
Memo memo;
memo.known = 0;
+ if (!argue(app, table, &ev)) return NULL;
for (bool more = pure_list_run(list, 0, &run); more; more = pure_list_next(list, &run)) {
Lanes keep;
if (run_lanes(m, app->values, m->view_code[table], &ev, &run, &keep, store, &memo, NULL)) {
@@ -2628,6 +2707,7 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) {
for (size_t item = 0, items = pure_list_count(list); item < items; ++item)
if (!keep_evaluated(app, table, rows, item)) return NULL;
rows->changes = app->changes;
+ rows->given = app->arguments_given;
return rows;
}
diff --git a/core/module.h b/core/module.h
index 44af5eb..0b20a9f 100644
--- a/core/module.h
+++ b/core/module.h
@@ -58,9 +58,13 @@ typedef enum { PURE_LESS, PURE_LESS_EQUAL, PURE_GREATER, PURE_GREATER_EQUAL, PUR
/* A timer ticks every `period` milliseconds while a host runs the app. An
* output is a message the app sends (SEND, in a handler; it has fields as a
- * message has and no handler): what receives it is the host's wiring. */
+ * message has and no handler): what receives it is the host's wiring.
+ * Arguments are the fields a view takes (a view with arguments, its
+ * columns and its filter read them as a message handler reads its message);
+ * such an input has no handler and names its view, whose name it bears
+ * with .arguments after it. */
typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2, PURE_KIND_TEXT = 3, PURE_KIND_TIMER = 4,
- PURE_KIND_OUTPUT = 5 } PureInputKind;
+ PURE_KIND_OUTPUT = 5, PURE_KIND_ARGUMENTS = 6 } PureInputKind;
enum { PURE_TIMER_LONGEST = 86400000, PURE_SOURCE = 64 };
/* A message field: a u32le or a binary64 (fixed, at its offset in the
* payload's prefix), or bytes, text or rows of any length. The prefix also
@@ -109,6 +113,7 @@ typedef struct {
uint16_t field_start, field_count;
uint32_t prefix; /* fixed payload bytes before the variable fields */
uint32_t period; /* a timer's, in milliseconds */
+ uint32_t view; /* arguments: the view they are for */
} PureInputShape;
/* A record type: fields numbered from first, each a number or text. */
@@ -145,6 +150,7 @@ typedef struct {
uint16_t view_list[PURE_MODULE_VIEWS]; /* the list of a table or column */
PureBlock view_code[PURE_MODULE_VIEWS];
uint8_t view_plan[PURE_MODULE_VIEWS]; /* a table's filter runs a leaf of its list at once (as a fold may) */
+ uint8_t view_arguments[PURE_MODULE_VIEWS]; /* the input of a view's arguments + 1 (a column's: its table's), or 0 */
PureShape shape[PURE_MODULE_SHAPES];
uint8_t shapes;
PureHandler handler[PURE_MODULE_HANDLERS];
@@ -253,7 +259,7 @@ typedef struct {
* sets it, so a script can replay any time; views never see it. */
double now;
uint64_t changes; /* counts every change of states, never back: what a filter's rows are fresh for */
- struct PureRows { uint32_t table; uint64_t changes; size_t *items, count, capacity; } *rows; /* a filtered table's items */
+ struct PureRows { uint32_t table; uint64_t changes, given; size_t *items, count, capacity; } *rows; /* a filtered table's items */
unsigned row_tables;
/* Current message or text event, kept until pure_app_finish. */
void *document;
@@ -279,6 +285,12 @@ typedef struct {
bool links_changed; /* since the last save */
bool duplicate; /* the last delivery was heard before and not applied */
struct { const char *source; uint64_t seq; bool pending; } delivering;
+ /* The arguments given for a view (pure_app_arguments): its input + 1 (0:
+ * none), where their fields are, and a count of the times they were given. */
+ unsigned argued;
+ PureSpan arguments;
+ uint32_t argument_spans[PURE_MODULE_FIELDS][2];
+ uint64_t arguments_given;
/* Passes over lists that answer shaped folds, by the list's version. */
struct PurePass *passes;
unsigned pass_count;
@@ -331,6 +343,15 @@ PureResult pure_app_view_text(PureApp *, uint32_t view, bool (*visit)(void *, Pu
/* The rows of a table view (its list's items, or those its filter keeps),
* or 0 for another view. A host evaluates only the cells it shows. */
size_t pure_app_rows(PureApp *, uint32_t table);
+/* A view that takes arguments (a column: its table's): the input whose
+ * fields they are, or -1. */
+int pure_module_arguments(const PureModule *, uint32_t view);
+/* The arguments of view, as a message payload of their input's shape (its
+ * variable fields where placed says, or after the prefix in order): the
+ * view, its columns and its rows read them until they are given again. The
+ * payload stays the caller's and must outlive what it shows. INVALID when
+ * the view takes none, LIMIT, NON_FINITE or ENCODING as a message fails. */
+PureResult pure_app_arguments(PureApp *, uint32_t view, PureSpan payload, const uint32_t *placed);
/* How many passes the app keeps, made to answer shaped folds in columns. */
unsigned pure_app_passes(const PureApp *);
/* The item a row of a table shows, and the row that shows an item (false:
diff --git a/core/test-module.c b/core/test-module.c
index 6117fe4..3b697b3 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -25,7 +25,8 @@ int pure_store_test_fault(PureStoreStage stage) {
typedef struct { const char *name; double initial; } S;
typedef struct { const char *name; uint32_t type; } F;
-/* kind 0 is a key input; otherwise PURE_KIND_MESSAGE with fields. */
+/* kind 0 is a key input; otherwise PURE_KIND_MESSAGE with fields. Arguments
+ * name their view by its number in key. */
typedef struct { const char *name, *key; int kind; bool keyed; const F *fields; size_t nfields; } I;
#define KEY(name, key) {name, key, 0, false, NULL, 0}
/* A timer's period (milliseconds) rides in nfields. */
@@ -126,8 +127,10 @@ static size_t build(unsigned char *out, Spec spec) {
p = put32(p, offset[n++]);
*p++ = (unsigned char)(in->kind ? in->kind : 1); *p++ = in->keyed; *p++ = 0; *p++ = 0;
bool timer = in->kind == PURE_KIND_TIMER;
- p = put32(p, timer ? (uint32_t)in->nfields : in->kind ? 0 : offset[n++]);
- uint32_t first = in->kind == PURE_KIND_MESSAGE || in->kind == PURE_KIND_OUTPUT ? field_start : 0; /* only messages and outputs have fields */
+ p = put32(p, timer ? (uint32_t)in->nfields : in->kind == PURE_KIND_ARGUMENTS ? (uint32_t)strtoul(in->key, NULL, 10) :
+ in->kind ? 0 : offset[n++]);
+ uint32_t first = in->kind == PURE_KIND_MESSAGE || in->kind == PURE_KIND_OUTPUT || in->kind == PURE_KIND_ARGUMENTS
+ ? field_start : 0; /* only messages, outputs and arguments have fields */
*p++ = (unsigned char)first; *p++ = (unsigned char)(first >> 8);
*p++ = (unsigned char)(timer ? 0 : in->nfields); *p++ = 0;
for (size_t f = 0; !timer && f < in->nfields; ++f) field_offsets[nf++] = offset[n++];
@@ -2667,6 +2670,135 @@ static void walk_mutations(void) {
printf("{\"walk_module_mutations\":20000,\"accepted_after_damage\":%u}\n", accepted);
}
+/* ---- Views that take arguments -----------------------------------------
+ * balance(aid) finds an account by key, rich(min) counts accounts above an
+ * argument (a leaf at a time), echo(name) shows a text argument as it is,
+ * branch(b) is a table filtered by an argument whose column folds with it;
+ * a handler's fold reads its message. Given again, arguments are read
+ * anew: a filter's rows are kept for the arguments they were made for. */
+enum { A_N = 0, A_ACCOUNTS = 1 };
+static const S argued_numbers[] = {{"n", 0}};
+static const T argued_lists[] = {{"accounts", 3 | 1 << 8, 0}};
+static const RF account_fields[] = {{"aid", 0, 0}, {"bid", 0, 0}, {"balance", 0, 0}};
+static const R argued_records[] = {{"Account", account_fields, 3}};
+static const F pay_fields[] = {{"b", 1}}, add_fields[] = {{"aid", 1}, {"bid", 1}, {"balance", 3}},
+ aid_fields[] = {{"aid", 1}}, min_fields[] = {{"min", 3}}, name_fields[] = {{"name", 4}}, b_fields[] = {{"b", 1}};
+static const I argued_inputs[] = {
+ {"pay", NULL, PURE_KIND_MESSAGE, false, pay_fields, 1}, {"add", NULL, PURE_KIND_MESSAGE, false, add_fields, 3},
+ {"balance.args", "0", PURE_KIND_ARGUMENTS, false, aid_fields, 1},
+ {"rich.args", "1", PURE_KIND_ARGUMENTS, false, min_fields, 1},
+ {"echo.args", "2", PURE_KIND_ARGUMENTS, false, name_fields, 1},
+ {"branch.args", "3", PURE_KIND_ARGUMENTS, false, b_fields, 1}};
+static const O argued_code[] = {
+ /* 0 balance(aid) = accounts[aid].balance */
+ {FIELD, 0, 0}, {PURE_OP_LIST_FIND, A_ACCOUNTS, 0}, {IG, ITEM(A_ACCOUNTS, 2), 0},
+ /* 3 rich(min) = count(a in accounts where a.balance > min) */
+ {FOLD, FOLDING(PURE_FOLD_COUNT, A_ACCOUNTS, 4), 0}, {ROW, A_ACCOUNTS, 0}, {IG, ITEM(A_ACCOUNTS, 2), 0}, {FIELD, 0, 0},
+ {GT, 0, 0},
+ /* 8 echo(name) = name */
+ {FIELD, 0, 0},
+ /* 9 branch(b): each a in accounts where a.bid == b */
+ {ROW, A_ACCOUNTS, 0}, {IG, ITEM(A_ACCOUNTS, 1), 0}, {FIELD, 0, 0}, {EQ, 0, 0},
+ /* 13 aid = a.aid */
+ {ROW, A_ACCOUNTS, 0}, {IG, ITEM(A_ACCOUNTS, 0), 0},
+ /* 15 share = a.balance / sum(x.balance for x in accounts where x.bid == b) */
+ {ROW, A_ACCOUNTS, 0}, {IG, ITEM(A_ACCOUNTS, 2), 0}, {FOLD, FOLDING(PURE_FOLD_SUM, A_ACCOUNTS, 6), 0}, {ROW, A_ACCOUNTS, 0},
+ {IG, ITEM(A_ACCOUNTS, 2), 0}, {ROW, A_ACCOUNTS, 0}, {IG, ITEM(A_ACCOUNTS, 1), 0}, {FIELD, 0, 0}, {EQ, 0, 0},
+ {DIV, 0, 0},
+ /* 25 on pay: n = count(a in accounts where a.bid == pay.b) */
+ {FOLD, FOLDING(PURE_FOLD_COUNT, A_ACCOUNTS, 4), 0}, {ROW, A_ACCOUNTS, 0}, {IG, ITEM(A_ACCOUNTS, 1), 0}, {FIELD, 0, 0},
+ {EQ, 0, 0}, {ST, A_N, 0},
+ /* 31 on add: accounts.add(Account { aid, bid, balance }) */
+ {FIELD, 0, 0}, {FIELD, 1, 0}, {FIELD, 2, 0}, {LA, A_ACCOUNTS, 0},
+};
+static const V argued_views[] = {{"balance", 0, 3}, {"rich", 3, 5}};
+static const V argued_texts[] = {{"echo", 8, 1}};
+static const W argued_tables[] = {{"branch", 2 | (A_ACCOUNTS + 1) << 8, 0, 9, 4}, {"aid", 0, 4, 13, 2}, {"share", 0, 4, 15, 10}};
+static const H argued_handlers[] = {{1, 25, 6}, {3, 31, 4}};
+
+static Spec argued_spec(const I *inputs, size_t ninputs, const H *handlers, size_t nhandlers) {
+ return (Spec){argued_numbers, 1, inputs, ninputs, argued_views, 2, handlers, nhandlers, argued_code, LEN(argued_code), NULL, 0,
+ argued_lists, 1, NULL, 0, argued_texts, 1, argued_records, 1, argued_tables, 3};
+}
+
+static PureResult give(PureApp *app, uint32_t view, const unsigned char *payload, size_t size) {
+ return pure_app_arguments(app, view, (PureSpan){payload, size}, NULL);
+}
+
+static void view_arguments(void) {
+ CHECK(load(argued_spec(argued_inputs, LEN(argued_inputs), argued_handlers, 2), &module_a) == PURE_OK);
+ CHECK(pure_module_arguments(&module_a, 0) == 2 && pure_module_arguments(&module_a, 5) == 5 &&
+ pure_module_arguments(&module_a, 3) == 5 && module_a.code[3].plan && module_a.code[25].plan);
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ for (uint32_t aid = 1; aid <= 30; ++aid) {
+ unsigned char payload[16];
+ put32(payload, aid);
+ put32(payload + 4, aid % 3);
+ put_double(payload + 8, aid * 10.0);
+ CHECK(pure_app_message(&app, 1, NULL, -1, (PureSpan){payload, 16}) == PURE_OK);
+ }
+ double value;
+ unsigned char four[8];
+ CHECK(!pure_app_view(&app, 0, &value)); /* no arguments given */
+ put32(four, 7);
+ CHECK(give(&app, 0, four, 4) == PURE_OK && pure_app_view(&app, 0, &value) && value == 70);
+ put32(four, 99);
+ CHECK(give(&app, 0, four, 4) == PURE_OK && !pure_app_view(&app, 0, &value)); /* no such account */
+ CHECK(give(&app, 0, four, 3) == PURE_LIMIT && !pure_app_view(&app, 0, &value));
+ for (double min = -1; min <= 310; min += 7.5) {
+ put_double(four, min);
+ size_t above = 0;
+ for (uint32_t aid = 1; aid <= 30; ++aid) above += aid * 10.0 > min;
+ CHECK(give(&app, 1, four, 8) == PURE_OK && pure_app_view(&app, 1, &value) && value == (double)above);
+ }
+ put_double(four, NAN);
+ CHECK(give(&app, 1, four, 8) == PURE_INVALID);
+ const char *name = "argument: ν•œκΈ€ βœ“";
+ CHECK(give(&app, 2, (const unsigned char *)name, strlen(name)) == PURE_OK);
+ Text shown = {{0}, 0};
+ CHECK(pure_app_view_text(&app, 2, text_span, &shown) == PURE_OK && shown.size == strlen(name) &&
+ !memcmp(shown.text, name, shown.size));
+ CHECK(give(&app, 2, (const unsigned char *)"\xff", 1) == PURE_INVALID);
+ for (uint32_t b = 0; b < 3; ++b) {
+ put32(four, b);
+ CHECK(give(&app, 3, four, 4) == PURE_OK && pure_app_rows(&app, 3) == 10);
+ double total = 0;
+ for (uint32_t aid = 1; aid <= 30; ++aid) if (aid % 3 == b) total += aid * 10.0;
+ for (size_t row = 0; row < 10; ++row) {
+ double aid, share;
+ CHECK(pure_app_cell(&app, 4, row, &aid) && (uint32_t)aid % 3 == b && pure_app_cell(&app, 5, row, &share) &&
+ share == aid * 10.0 / total);
+ }
+ }
+ put32(four, 2);
+ CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){four, 4}) == PURE_OK && app.values[A_N] == 10);
+ pure_app_stop(&app);
+
+ /* What arguments may not be. */
+ static const I on_column[] = {{"pay", NULL, PURE_KIND_MESSAGE, false, pay_fields, 1},
+ {"aid.args", "4", PURE_KIND_ARGUMENTS, false, aid_fields, 1}};
+ static const I misnamed[] = {{"pay", NULL, PURE_KIND_MESSAGE, false, pay_fields, 1},
+ {"balance.argz", "0", PURE_KIND_ARGUMENTS, false, aid_fields, 1}};
+ static const I twice[] = {{"pay", NULL, PURE_KIND_MESSAGE, false, pay_fields, 1},
+ {"balance.args", "0", PURE_KIND_ARGUMENTS, false, aid_fields, 1},
+ {"balance.args", "0", PURE_KIND_ARGUMENTS, false, aid_fields, 1}};
+ static const H handled[] = {{5, 31, 4}};
+ const struct { Spec spec; const char *reason; } refused[] = {
+ {argued_spec(on_column, 2, NULL, 0), "arguments"},
+ {argued_spec(misnamed, 2, NULL, 0), "arguments"},
+ {argued_spec(twice, 3, NULL, 0), "arguments"},
+ {argued_spec(argued_inputs, LEN(argued_inputs), handled, 1), "handler event"},
+ {argued_spec(argued_inputs, 2, NULL, 0), "view with an effect"}, /* FIELD without arguments */
+ };
+ for (size_t i = 0; i < LEN(refused); ++i)
+ if (load(refused[i].spec, &module_b) != PURE_INVALID || !strstr(error, refused[i].reason)) {
+ fprintf(stderr, "arguments case %zu: expected '%s', got '%s'\n", i, refused[i].reason, error);
+ abort();
+ }
+ printf("{\"view_arguments\":{\"accounts\":30,\"branches\":3}}\n");
+}
+
/* Damaged fold and filter modules: whatever loads runs safely. */
static void fold_mutations(void) {
static unsigned char valid[65536], bytes[65536];
@@ -3590,6 +3722,7 @@ int main(void) {
lane_folds();
walks();
walk_mutations();
+ view_arguments();
keyed();
keyed_mutations();
snapshots();