From c7a4178b1b2b8702cc2798aa3d31447bd02389e4 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 28 Sep 2026 00:21:09 +0100 Subject: Let a fold that runs in a column read the column's row OUTER_ROW pushes the row of the column a fold's block runs for, as an index of the column's list; ROW inside the block stays the fold's own item. The verifier accepts it only in a fold's block inside a column, naming that column's list (a fold in a handler, a plain view or a filter has no outer row). So a column can hold a correlated fold: per branch, the sum of its accounts; per account, how many are richer. aup compiles the column's row inside a fold to it. --- core/module.c | 47 +++++++++++++++++++++++++++++++---------------- core/module.h | 2 +- 2 files changed, 32 insertions(+), 17 deletions(-) (limited to 'core') diff --git a/core/module.c b/core/module.c index 7adef93..32d0629 100644 --- a/core/module.c +++ b/core/module.c @@ -14,14 +14,15 @@ 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, T_KEY = 8 }; 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, IN_ROW = 32 }; +enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32, IN_OUTER = 64 }; 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 * collection, a constant, a unit, the column's row, a branch target, a stack - * depth, a fold, a relation or a keyed list. */ + * depth, a fold, a relation, a keyed list, an output, or the row of the + * column a fold runs in. */ enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_INDEX + 1, A_FIELD, A_COLLECTION, A_CONSTANT, A_UNIT, - A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT }; + A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER }; /* A fold's block is verified as a column that leaves one number (count) or * two (a value and whether to include it), a table's filter as a column that * leaves one: these stand for a view type. */ @@ -90,6 +91,8 @@ static const struct { unsigned char pops, in[3], out, where, arg, of; } signatur [PURE_OP_INDEX_LAST] = {0, {0}, T_INDEX, READ, STATE(LIST), O_ARG}, [PURE_OP_INDEX_NUM] = {1, {T_INDEX}, T_NUM, READ, A_NONE, O_ANY}, [PURE_OP_ROW] = {0, {0}, T_INDEX, IN_ROW, A_ROW, O_ARG}, + /* In a fold that runs in a column, the column's row (the fold's own item is ROW's). */ + [PURE_OP_OUTER_ROW] = {0, {0}, T_INDEX, IN_OUTER, A_OUTER, O_ARG}, /* Stack copies: their types come from the values they move. */ [PURE_OP_PICK] = {0, {0}, 0, READ, A_DEPTH, O_ANY}, [PURE_OP_SLIDE] = {0, {0}, 0, READ, A_DEPTH, O_ANY}, @@ -180,7 +183,8 @@ 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; } -PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg, const PureInputShape *shape, int row) { +PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg, const PureInputShape *shape, int row, + int outer) { switch (kind) { case A_ITEM: return state_of(m, list_of(arg), PURE_STATE_LIST) && item_field(arg) < m->record[m->state_ref[list_of(arg)]].count; @@ -189,6 +193,7 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg case A_CONSTANT: return arg < m->constants; case A_UNIT: return arg <= PURE_UNIT_LINE; case A_ROW: return (int)arg == row; + case A_OUTER: return outer >= 0 && (int)arg == outer; case A_FOLD: return (arg & 0xFF) <= PURE_FOLD_FIRST && state_of(m, arg >> 8 & 0xFF, PURE_STATE_LIST) && arg >> 16; case A_RELATION: return arg <= PURE_NOT_EQUAL; case A_KEYED: return state_of(m, arg, PURE_STATE_LIST) && m->state_key[arg]; @@ -217,16 +222,19 @@ static bool same_stack(const Join *join, int height, const unsigned char *types, * in it); the block around treats it as one instruction and may not branch * into it. Returns why the block is refused, with at moved to the * instruction when one is at fault, or NULL. */ -PURE_COLD static const char *verify_block(const PureModule *, PureBlock, int view, int row, uint32_t event, uint32_t *at); +PURE_COLD static const char *verify_block(const PureModule *, PureBlock, int view, int row, int outer, uint32_t event, + uint32_t *at); /* target marks, per instruction of the block, a branch into it with an empty stack. */ -PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, int view, int row, uint32_t event, - uint32_t *at, unsigned char *target) { +/* outer: the row of the column a fold's block runs in (-1: none). */ +PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, int view, int row, int outer, + uint32_t event, uint32_t *at, unsigned char *target) { #define REFUSE(why) do { *at = pc; return why; } while (0) if (view >= 0 && !block.length) return "empty view"; int input = view >= 0 ? -1 : event_input(event); const PureInputShape *shape = input >= 0 ? &m->input[input] : NULL; - unsigned where = view >= 0 ? IN_VIEW | (row >= 0 ? IN_ROW : 0) : IN_HANDLER; + unsigned where = view >= 0 ? IN_VIEW | (row >= 0 ? IN_ROW : 0) | (view >= FOLD_ONE && outer >= 0 ? IN_OUTER : 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]; @@ -259,6 +267,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i unsigned allowed = signature[op->op].where, kind = signature[op->op].arg, of = signature[op->op].of; if (!(allowed & where)) { if (allowed & IN_ROW) REFUSE("row outside a column"); + if (allowed & IN_OUTER) REFUSE("outer row outside a fold in a column"); if (view == FILTER) REFUSE("filter with an effect"); if (view >= FOLD_ONE) REFUSE("fold with an effect"); if (view >= 0) REFUSE("view with an effect"); @@ -269,7 +278,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i uint32_t arg = op->arg; if ((kind == A_NONE && arg) || (op->op == PURE_OP_CONST ? !isfinite(op->value) : op->value != 0)) REFUSE("non-canonical operand"); - if (!names_arg(m, kind, arg, shape, row)) REFUSE("operand names no such state, field, constant or unit"); + if (!names_arg(m, kind, arg, shape, row, outer)) REFUSE("operand names no such state, field, constant or unit"); /* A keyed list places its items by key, and an item's key stays. */ if (op->op == PURE_OP_LIST_INSERT && m->state_key[arg]) REFUSE("insert by position into a keyed list"); if (op->op == PURE_OP_ITEM_SET && m->state_key[list_of(arg)] == item_field(arg) + 1) @@ -294,7 +303,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i unsigned how = arg & 0xFF; const char *why = verify_block(m, (PureBlock){pc + 1, skip}, how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE : FOLD_TWO, - (int)(arg >> 8 & 0xFF), 0, &inner); + (int)(arg >> 8 & 0xFF), row, 0, &inner); if (why) { *at = inner; return why; } for (uint32_t k = i + 1; k <= i + skip; ++k) { if (target[k]) REFUSE("branch into a fold"); @@ -380,12 +389,12 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i #undef REFUSE } -PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block, int view, int row, uint32_t event, - uint32_t *at) { +PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block, int view, int row, int outer, + uint32_t event, uint32_t *at) { if (block.start > m->code_count || block.length > m->code_count - block.start) return "code range outside module"; unsigned char *target = calloc((size_t)block.length + 1, 1); if (!target) return "out of memory"; - const char *why = verify_code(m, block, view, row, event, at, target); + const char *why = verify_code(m, block, view, row, outer, event, at, target); free(target); return why; } @@ -653,11 +662,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule bool table = out->view_type[i] == PURE_VIEW_TABLE; 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, 0, &bad))) goto refused; + table || out->view_table[i] ? out->view_list[i] : -1, -1, 0, &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, out->handler[i].event, &bad))) goto refused; + if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event, &bad))) goto refused; } return PURE_OK; refused: @@ -714,7 +723,8 @@ typedef struct { int64_t base; /* the revision a message's edit was made against, or -1 */ PureArena *arena; double dt; - size_t row; /* the row a column is evaluated for */ + 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 */ bool modified, exists; uint32_t (*spans)[2]; /* a message's variable fields: start and size in the payload */ } Event; @@ -1000,6 +1010,10 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even stack[sp++].bytes.owner = (uint16_t)arg; continue; } + case PURE_OP_OUTER_ROW: + stack[sp].num = (double)ev->outer; + stack[sp++].bytes.owner = (uint16_t)arg; + continue; case PURE_OP_CUR_MOVE: case PURE_OP_INDEX_MOVE: { double count = stack[--sp].num; Slot *at = &stack[sp - 1]; @@ -1151,6 +1165,7 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even double total = 0; bool any = false; Event per = *ev; + per.outer = ev->row; for (size_t row = 0, items = pure_list_count(list); row < items; ++row) { Slot out[2]; size_t mark = arena->used; diff --git a/core/module.h b/core/module.h index b9a3450..328c358 100644 --- a/core/module.h +++ b/core/module.h @@ -38,7 +38,7 @@ typedef enum { PURE_OP_ITEM_GET, PURE_OP_ITEM_SET, PURE_OP_INDEX_LOAD, PURE_OP_INDEX_STORE, PURE_OP_INDEX_MOVE, PURE_OP_INDEX_FIRST, PURE_OP_INDEX_LAST, PURE_OP_INDEX_NUM, PURE_OP_ROW, PURE_OP_PICK, PURE_OP_SLIDE, PURE_OP_FOLD, PURE_OP_TEXT_COMPARE, PURE_OP_LIST_FIND, PURE_OP_LIST_HAS, - PURE_OP_NOW, PURE_OP_FLOOR, PURE_OP_MOD, PURE_OP_SEND, + PURE_OP_NOW, PURE_OP_FLOOR, PURE_OP_MOD, PURE_OP_SEND, PURE_OP_OUTER_ROW, PURE_OP_COUNT } PureOpcode; /* FOLD's argument: the kind (bits 0-7), the list state (8-15) and the length -- cgit v1.2.3