diff options
| author | eunhhu <[email protected]> | 2026-09-28 04:51:16 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-28 04:51:16 +0100 |
| commit | 9542bf3faa27373076cb4b31dac01c34570962fb (patch) | |
| tree | 5743ec16534e30b11bf3e99abeef6260b2a86b26 /core | |
| parent | 92290c5102e7fbb1248a0f215b1099de4d00c143 (diff) | |
| download | pure-9542bf3faa27373076cb4b31dac01c34570962fb.tar.gz pure-9542bf3faa27373076cb4b31dac01c34570962fb.zip | |
Let a view take arguments
An input of the new kind 6 holds the arguments a view takes: fields as a
message has, no handler, the view's number where a key input names its
key, and the view's name with .args after it; one set a view, never a
column's (a table's columns and filter read the table's). The view, its
columns and filter, and the folds in them read their arguments with
FIELD, verified as a handler reads its message (IN_ARGUMENTS: reads, no
reply or document), and a fold anywhere may now read the message or
arguments of the block it is in: a handler's count(a in accounts where
a.bid == pay.bid) verifies.
pure_app_arguments gives a view's arguments as a payload of their shape
(placed as a message's fields); the view shows nothing until they are
given, and a filtered table keeps its rows for the arguments they were
made for. A text argument a view shows stays in the caller's payload
(FROM_ARGUMENTS), so it outlives the evaluation. A fold reading a number
argument still runs a leaf at a time (the same value for every item), and
a shaped fold takes arguments only on its key's side, so its pass does not
depend on them. Message fields are placed by one function for messages
and arguments.
view_arguments checks a keyed lookup by argument, a count above an
argument for 43 values against a model, a text argument shown back as it
is, a table filtered by an argument whose column folds with it (rows kept
per arguments), a handler's fold over its message, and five refusals.
Diffstat (limited to 'core')
| -rw-r--r-- | core/module.c | 178 | ||||
| -rw-r--r-- | core/module.h | 27 | ||||
| -rw-r--r-- | core/test-module.c | 139 |
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(); |
