diff options
Diffstat (limited to 'core')
| -rw-r--r-- | core/module.c | 217 | ||||
| -rw-r--r-- | core/module.h | 53 | ||||
| -rw-r--r-- | core/test-module.c | 146 |
3 files changed, 348 insertions, 68 deletions
diff --git a/core/module.c b/core/module.c index 2fbd37c..e7118f0 100644 --- a/core/module.c +++ b/core/module.c @@ -11,7 +11,7 @@ enum { FUNCTION_BYTES = 12, SIGNATURE_BYTES = 4 }; enum { STATE_BYTES = 16, INPUT_BYTES = 16, FIELD_BYTES = 8, - CONSTANT_BYTES = 8, RECORD_BYTES = 8, RECORD_FIELD_BYTES = 16, VIEW_BYTES = 16, + CONSTANT_BYTES = 8, RECORD_BYTES = 8, RECORD_FIELD_BYTES = 16, VIEW_BYTES = 20, HANDLER_BYTES = 12, OP_BYTES = 16 }; /* 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, T_LOCAL for what @@ -49,7 +49,7 @@ enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_CONSTA * item (STEPS_WALK); neither changes a state. A function's block begins with * its parameters on the stack and ends at RETURN alone (FUNCTION); the loops' * blocks in it are a handler's code. */ -enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK, STEPS, STEPS_WALK, FUNCTION }; +enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK, STEPS, STEPS_WALK, FUNCTION, LIST_OF = 200 }; /* LIST_OF + r: a list of r's */ /* The text or list an opcode works on: its cursor and index operands belong * to it, and so does a cursor or index it pushes. O_ANY: no rule. O_ARG: the * argument. O_REF: the text or list of the argument's cursor or index state. @@ -352,6 +352,10 @@ static uint32_t nested_record(const PureModule *m, uint32_t field) { return m->record_field_type[field] == PURE_RECORD_LIST ? (uint32_t)m->record_field_default[field].i64 : UINT32_MAX; } +/* A list view (not a list column), and one over a list state (not computed: its rows that state's items) (027's 4e). */ +static bool list_view(const PureModule *m, uint32_t view) { return m->view_type[view] == PURE_VIEW_LIST && !m->view_table[view]; } +static bool over_state(const PureModule *m, uint32_t view) { return list_view(m, view) && !m->view_computed[view]; } + /* A list kept past an event: stored or transient, not an event's (027's stage 3). */ static bool kept_list(const PureModule *m, uint32_t list) { return !(m->state_flags[list] & PURE_LIST_ROWS >> 8); } /* The steps of a kept list's lists, apart from the instructions' own (027's stage 3): the load gives ITEM_GET and @@ -765,7 +769,10 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i return height != 2 || types[0] != T_NUM || types[1] != T_BOOL ? "fold block must leave a number and a bool" : NULL; if (view == FOLD_I64) return height != 2 || types[0] != T_I64 || types[1] != T_BOOL ? "fold block must leave an i64 and a bool" : NULL; - unsigned char result = view == PURE_VIEW_TEXT ? T_TEXT : view == PURE_VIEW_BOOL ? T_BOOL : view == PURE_VIEW_I64 ? T_I64 : T_NUM; + if (view >= LIST_OF) /* a list view's or a list column's: a list of its record's items */ + return height != 1 || types[0] != T_LIST || owners[0] != view - LIST_OF ? "view must leave one value of its type" : NULL; + unsigned char result = view == PURE_VIEW_TEXT ? T_TEXT : view == PURE_VIEW_BOOL ? T_BOOL : view == PURE_VIEW_I64 ? T_I64 : + view == PURE_VIEW_BYTES ? T_BYTES : T_NUM; if (!handler ? height != 1 || types[0] != result : height != 0) return !handler ? "view must leave one value of its type" : "handler must leave no value"; return NULL; @@ -1843,30 +1850,53 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if ((out->state_type[i] == PURE_STATE_CURSOR || out->state_type[i] == PURE_STATE_INDEX) && !(out->transient >> i & 1) && out->transient >> out->state_ref[i] & 1) REFUSE("stored position in a transient state", i); - /* 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) { + /* Views (027's 4e): a list view names its list state (+ 1; its block a filter) or none (its block leaves its list), + * and its items' record (+ 1; a map of a list state 0, its columns following it, each naming it + 1). */ + uint32_t declared = out->views; + for (uint32_t i = 0; i < declared; ++i, at += VIEW_BYTES) { if (!name_at(text, strings, read32(at), NAME_IDENTIFIER, out->view_names[i])) REFUSE("view name", i); 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_I64 || (at[5] && at[4] != PURE_VIEW_TABLE) || - (table && (table > i || out->view_type[table - 1] != PURE_VIEW_TABLE || at[4] == PURE_VIEW_TABLE))) + out->view_record[i] = (uint16_t)read16(at + 8); + out->view_code[i] = (PureBlock){read32(at + 12), read32(at + 16)}; + uint32_t table = out->view_table[i], record = out->view_record[i], list = read16(at + 10); + bool listed = at[4] == PURE_VIEW_LIST; + if (at[4] > PURE_VIEW_BYTES || at[5] || listed != (record || (!table && list)) || record > out->records || + (table && (table > i || !over_state(out, table - 1) || out->view_record[table - 1] || list)) || + (!listed && list)) REFUSE("view type", i); - /* A table names its list in start, or, filtered, in the byte after its - * type (list + 1) with its filter's block in start and length. */ - if (at[4] == PURE_VIEW_TABLE) { - uint32_t list = at[5] ? at[5] - 1u : out->view_code[i].start; - if (!state_of(out, list, PURE_STATE_LIST) || !at[5] != !out->view_code[i].length) REFUSE("table list", i); - out->view_list[i] = (uint16_t)list; + if (listed && !table) { + if (list ? !state_of(out, list - 1, PURE_STATE_LIST) || (record && record - 1 != out->state_ref[list - 1]) + : !out->view_code[i].length) + REFUSE("list view's list", i); + out->view_list[i] = (uint16_t)(list ? list - 1 : 0); + out->view_computed[i] = !list; } else if (table) out->view_list[i] = out->view_list[table - 1]; } + /* A list view of a record (a list state as it is, or a list its block leaves) has the record's fields as its + * columns, made here: each reads its field of the row's item as it is. */ + for (uint32_t i = 0; i < declared; ++i) { + if (!list_view(out, i) || !out->view_record[i]) continue; + const PureRecord *r = &out->record[out->view_record[i] - 1]; + for (unsigned f = 0; f < r->count; ++f) { + if (out->views == PURE_MODULE_VIEWS) REFUSE("views and their columns", i); + uint32_t c = out->views++, field = r->first + f; + memcpy(out->view_names[c], out->record_field_names[field], PURE_MODULE_NAME); + out->view_type[c] = out->record_field_type[field]; + out->view_table[c] = (uint16_t)(i + 1); + out->view_list[c] = out->view_list[i]; + out->view_record[c] = (uint16_t)(out->view_type[c] == PURE_VIEW_LIST ? nested_record(out, field) + 1 : 0); + out->view_field[c] = (uint8_t)(f + 1); + out->view_computed[c] = out->view_computed[i]; + out->view_code[c] = (PureBlock){0, 0}; + } + } /* 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]; /* A pointing points at a table's rows (027's 4d). */ - if (shape->binding == PURE_BIND_POINT && (shape->view >= out->views || out->view_type[shape->view] != PURE_VIEW_TABLE)) + if (shape->binding == PURE_BIND_POINT && (shape->view >= declared || !over_state(out, shape->view))) REFUSE("pointing's table", i); if (shape->kind != PURE_KIND_ARGUMENTS) continue; char path[PURE_MODULE_NAME + 8]; @@ -1919,12 +1949,13 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule !strcmp(out->view_names[i], out->view_names[k])) REFUSE("duplicate column", i); for (uint32_t i = 0; i < out->views; ++i) { bad = i; - bool table = out->view_type[i] == PURE_VIEW_TABLE; + bool table = over_state(out, i); uint32_t event = out->view_arguments[i]; /* the arguments' input, as an arrival (its number + 1) */ - bool computes = !table && !out->view_table[i]; /* a view outside a table may have a body */ - 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, event, computes, 0, 0, -1, &bad))) + bool computes = !table && !out->view_table[i]; /* a view outside a list over a state may have a body */ + int kind = table ? FILTER : out->view_type[i] == PURE_VIEW_LIST ? LIST_OF + out->view_record[i] - 1 : out->view_type[i]; + if (!out->view_field[i] && (!table || out->view_code[i].length) && + (why = verify_block(out, out->view_code[i], kind, table || out->view_table[i] ? out->view_list[i] : -1, -1, event, + computes, 0, 0, -1, &bad))) goto refused; if (computes && !locals_agree(out, out->view_code[i])) { why = "local used as two things"; goto refused; } for (uint32_t pc = out->view_code[i].start; computes && pc < out->view_code[i].start + out->view_code[i].length; ++pc) { @@ -1980,12 +2011,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule for (uint32_t i = 0; i < out->views; ++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], given); + out->view_plan[i] = over_state(out, i) && out->view_code[i].length && block_has_lanes(out, out->view_code[i], given); PureRange range; if (out->view_plan[i] && block_range(out, out->view_code[i], out->view_list[i], true, &range)) out->view_range[i] = keep_range(out, &range); - if (out->view_type[i] != PURE_VIEW_TABLE) range_code(out, out->view_code[i], given); + if (!over_state(out, i)) range_code(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) { @@ -2085,6 +2115,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule op->op == PURE_OP_LIST_APPEND ? PURE_OP_KEPT_APPEND : op->op == PURE_OP_LIST_INSERT ? PURE_OP_KEPT_INSERT : PURE_OP_KEPT_APPEND_IN); } + /* A view or a column reading a kept list's list counts what it adds as a body does, from none at each evaluation + * (a reader's too, which no event began). */ + for (uint32_t i = 0; i < out->views; ++i) + for (uint32_t pc = out->view_code[i].start; pc < out->view_code[i].start + out->view_code[i].length; ++pc) + if (out->code[pc].op == PURE_OP_KEPT_GET) out->view_body[i] = true; out->runner = out->calls ? 2 : out->batches ? 1 : 0; out->jit = pure_jit_make(out); /* none: the executor runs it all */ return PURE_OK; @@ -4965,7 +5000,7 @@ void pure_app_stop(PureApp *app) { pure_save_close_start(app); pure_log_close(app->log); pure_undo_free(&app->undo); - for (unsigned i = 0; i < app->row_tables; ++i) free(app->rows[i].items); + for (unsigned i = 0; i < app->row_tables; ++i) free(app->rows[i].items), pure_list_free(app->rows[i].list); free(app->rows); drop_passes(app); free(app->arena.bytes); @@ -5405,12 +5440,51 @@ static bool keep_evaluated(PureApp *app, uint32_t table, struct PureRows *rows, return evaluate(app, table, item, &kept) != PURE_OK || kept.num == 0 || keep_row(rows, item); } -/* A filtered table's items, as fresh as the app's states: the filter runs - * once per item after each change (an item it fails on is left out), not - * once per cell. NULL for a table without a filter, or without room. */ +/* A computed list view's list (027's 4e): the one its block leaves, copied into a kept list (its lists in stored form) + * so its rows and cells outlive the evaluations a host asks for between them; false without room. */ +static __attribute__((cold, noinline)) bool computed(PureApp *app, uint32_t view, struct PureRows *rows) { + const PureModule *m = app->module; + uint32_t record = m->view_record[view] - 1u; + const PureRecord *r = &m->record[record]; + pure_list_free(rows->list); /* (another module's view, after a replace, may have been here) */ + if (!(rows->list = new_list(m, record, true))) return false; + Slot slot; + PureResult result = evaluate(app, view, 0, &slot); + rows->changes = app->changes; + rows->given = app->arguments_given; + rows->fault = result == PURE_OK ? PURE_FAULT_NONE : app->view_fault; + if (result != PURE_OK) return result != PURE_ALLOCATION; /* no rows: the fault says why */ + const PureList *made = slot.i64 ? app->nested.list[slot.i64 - 1] : NULL; + for (size_t at = 0, count = made ? pure_list_count(made) : 0; at < count; ++at) { + PureCell cells[PURE_RECORD_FIELDS]; + size_t room = 0; + for (unsigned f = 0; f < r->count; ++f) { + cells[f] = pure_list_get(made, at, f); + if (m->record_field_type[r->first + f] == PURE_RECORD_LIST) + room += value_size(app, nested_record(m, r->first + f), (uint64_t)cells[f].i64); + } + app->view_arena.used = 0; + if (!reserve(&app->view_arena, room)) return false; /* all of it first: no span moves while the next is made */ + for (unsigned f = 0; f < r->count; ++f) { + if (m->record_field_type[r->first + f] != PURE_RECORD_LIST) continue; + unsigned char *to = app->view_arena.bytes + app->view_arena.used; + size_t size = (size_t)(value_put(app, nested_record(m, r->first + f), (uint64_t)cells[f].i64, to) - to); + app->view_arena.used += size; + cells[f] = (PureCell){.text = to, .size = size}; + } + if (pure_list_insert(rows->list, at, cells, NULL) != PURE_OK || !keep_row(rows, at)) return false; + } + return true; /* its rows its items, in order (as a filter's are) */ +} + +/* A list view's rows, as fresh as the app's states: a filtered one's items + * (the filter runs once per item after each change, an item it fails on + * left out, not once per cell), or a computed one's list (its block run + * once after each change). NULL for a list state's view without a filter, + * or without room. */ static struct PureRows *filtered(PureApp *app, uint32_t table) { const PureModule *m = app->module; - if (!m->view_code[table].length) return NULL; + if (!m->view_computed[table] && !m->view_code[table].length) return NULL; struct PureRows *rows = NULL; for (unsigned i = 0; i < app->row_tables && !rows; ++i) if (app->rows[i].table == table) rows = &app->rows[i]; if (!rows) { @@ -5422,6 +5496,7 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) { } if (rows->changes == app->changes && (!m->view_arguments[table] || rows->given == app->arguments_given)) return rows; rows->count = 0; + if (m->view_computed[table]) return computed(app, table, rows) ? rows : NULL; /* A unit of work of its own, counted as written: the filter for every item. */ const PureList *list = app->lists[m->view_list[table]]; app->work = 0; @@ -5466,8 +5541,8 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) { bool pure_app_row_item(PureApp *app, uint32_t table, size_t row, size_t *item) { const PureModule *m = app->module; - if (table >= m->views || m->view_type[table] != PURE_VIEW_TABLE) return false; - if (!m->view_code[table].length) { + if (table >= m->views || !list_view(m, table)) return false; + if (!m->view_code[table].length) { /* (a computed one's has its block, and its rows as a filter's are) */ *item = row; if (row < pure_list_count(app->lists[m->view_list[table]])) return true; app->view_fault = PURE_FAULT_NONE; @@ -5484,7 +5559,7 @@ bool pure_app_row_item(PureApp *app, uint32_t table, size_t row, size_t *item) { bool pure_app_item_row(PureApp *app, uint32_t table, size_t item, size_t *row) { const PureModule *m = app->module; - if (table >= m->views || m->view_type[table] != PURE_VIEW_TABLE) return false; + if (table >= m->views || !list_view(m, table)) return false; if (!m->view_code[table].length) { *row = item; return item < pure_list_count(app->lists[m->view_list[table]]); @@ -5501,15 +5576,43 @@ bool pure_app_item_row(PureApp *app, uint32_t table, size_t item, size_t *row) { return true; } -/* 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) { +/* A view's type as it is lent: a bool's value is 0 or 1 (a number's), bytes as a text is. */ +static uint8_t lent_type(uint8_t type) { + return type == PURE_VIEW_BOOL ? PURE_VIEW_NUM : type == PURE_VIEW_BYTES ? PURE_VIEW_TEXT : type; +} + +/* A column the load made (027's 4e) at an item: its field as it is, a text's, bytes' or list's (in its stored form) + * copied into the view's arena. */ +static __attribute__((cold, noinline)) PureResult field_of(PureApp *app, uint32_t column, size_t item, Slot *slot) { + const PureModule *m = app->module; + uint32_t table = m->view_table[column] - 1u; + const PureList *items = m->view_computed[table] ? filtered(app, table)->list : app->lists[m->view_list[table]]; + PureCell cell = pure_list_get(items, item, m->view_field[column] - 1u); + app->view_fault = PURE_FAULT_NONE; + app->view_arena.used = 0; + uint8_t type = m->view_type[column]; + if (type != PURE_VIEW_TEXT && type != PURE_VIEW_BYTES && type != PURE_VIEW_LIST) { + slot->i64 = cell.i64; /* a number's, a bool's or an i64's bits */ + return PURE_OK; + } + if (!reserve(&app->view_arena, cell.size)) return PURE_ALLOCATION; + if (cell.size) memcpy(app->view_arena.bytes, cell.text, cell.size); + app->view_arena.used = cell.size; + slot->bytes = (PureBytes){FROM_ARENA, 0, {0, 0}, 0, cell.size}; + return PURE_OK; +} + +/* A view outside a list view (column false), or a column and a row of its list view (inlined in each, as it was before + * 4e added a caller). */ +static inline __attribute__((always_inline)) 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] == PURE_VIEW_BOOL ? PURE_VIEW_NUM : m->view_type[view]) != type || - (m->view_table[view] != 0) != column) { /* a bool's value is 0 or 1 */ + if (view >= m->views || lent_type(m->view_type[view]) != type || (m->view_table[view] != 0) != column) { app->view_fault = PURE_FAULT_NONE; return PURE_INVALID; } if (column && !pure_app_row_item(app, m->view_table[view] - 1u, row, &row)) return PURE_NOT_FOUND; /* it says why */ + if (m->view_field[view]) return field_of(app, view, row, slot); return evaluate(app, view, row, slot); /* and so does this */ } @@ -5535,10 +5638,11 @@ PureResult pure_app_view_text(PureApp *app, uint32_t view, bool (*visit)(void *, size_t pure_app_rows(PureApp *app, uint32_t table) { const PureModule *m = app->module; - if (table >= m->views || m->view_type[table] != PURE_VIEW_TABLE) return 0; + if (table >= m->views || !list_view(m, table)) return 0; struct PureRows *rows = filtered(app, table); app->view_fault = rows ? rows->fault : PURE_FAULT_NONE; - return rows ? rows->count : m->view_code[table].length ? 0 : pure_list_count(app->lists[m->view_list[table]]); + return rows ? rows->count + : m->view_computed[table] || m->view_code[table].length ? 0 : pure_list_count(app->lists[m->view_list[table]]); } bool pure_app_cell(PureApp *app, uint32_t column, size_t row, double *out) { @@ -5561,6 +5665,43 @@ PureResult pure_app_cell_text(PureApp *app, uint32_t column, size_t row, bool (* return result == PURE_OK ? visit_value(app, &app->view_arena, slot.bytes, visit, context) : result; } +PureResult pure_app_cell_items(PureApp *app, uint32_t column, size_t row, PureSpan *items) { + Slot slot; + PureResult result = view_of(app, column, true, row, PURE_VIEW_LIST, &slot); + if (result != PURE_OK) return result; + /* A made column's is a kept list's cell (a list state kept past an event, or a computed view's), in that form + * already; a map's column leaves one of the evaluation's lists, written in that form in the view's arena. */ + if (app->module->view_field[column]) { + *items = (PureSpan){app->view_arena.bytes + slot.bytes.offset, (size_t)slot.bytes.length}; + return PURE_OK; + } + PureCell field; + if (value_cell(app, &app->view_arena, app->module->view_record[column] - 1u, (uint64_t)slot.i64, &field) != PURE_OK) + return PURE_ALLOCATION; + *items = (PureSpan){field.text, field.size}; + return PURE_OK; +} + +bool pure_items_next(const PureModule *m, uint32_t record, PureSpan items, size_t *at, PureCell *cells) { + if (record >= m->records || *at >= items.size) return false; + const PureRecord *r = &m->record[record]; + size_t from = *at; + for (unsigned f = 0; f < r->count; ++f) { + uint8_t type = m->record_field_type[r->first + f]; + if (pure_record_bytes(type) || type == PURE_RECORD_LIST) { + if (items.size - from < 4 || read32(items.data + from) > items.size - from - 4) return false; + cells[f] = (PureCell){.text = (unsigned char *)items.data + from + 4, .size = read32(items.data + from)}; + from += 4 + cells[f].size; + } else { + if (items.size - from < 8) return false; + cells[f] = (PureCell){.i64 = read_i64(items.data + from)}; + from += 8; + } + } + *at = from; + return true; +} + PureSnapshot *pure_app_snapshot(PureApp *app) { return pure_save_snapshot(app, UINT32_MAX); } /* What no snapshot reads any more back to the app's lists, to make their diff --git a/core/module.h b/core/module.h index 7cf90b9..20e5fba 100644 --- a/core/module.h +++ b/core/module.h @@ -283,13 +283,19 @@ enum { PURE_CURSOR_STAYS = 1, PURE_LIST_ROWS = 256, PURE_LIST_BOUND = 512 }; * value (docs/design/018). A stored cursor or index is of a stored text or * list. */ enum { PURE_STATE_TRANSIENT = 256 }; -/* A table view has one row per item of a list, or per item its filter - * keeps; its columns are number, bool, i64 or text views evaluated for one - * row at a time. A view outside a table may compute (docs/design/018): its - * code may hold LOOP, EACH, BREAK and locals, and change an event's lists, - * but no state; each evaluation begins with its locals 0 and those lists - * empty (a reader's own: readers of one snapshot compute apart). */ -typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2, PURE_VIEW_BOOL = 3, PURE_VIEW_I64 = 4 } PureViewType; +/* A view's type is a record field's (027's 4e). A list view's rows are a + * list's items: a list state's (its filter's, when it has one), or those of + * the list its code leaves (computed once a change, kept for its rows and + * cells until the next). Its columns are its record's fields, read from the + * items as they are, or for a list state's map (a view of no record) the + * views that follow it, each evaluated for one row at a time. A list cell + * is lent in its items' stored form (pure_app_cell_items). A view outside + * a list may compute (docs/design/018): its code may hold LOOP, EACH, BREAK + * and locals, and change an event's lists, but no state; each evaluation + * begins with its locals 0 and those lists empty (a reader's own: readers + * of one snapshot compute apart). */ +typedef enum { PURE_VIEW_NUM = PURE_RECORD_NUMBER, PURE_VIEW_TEXT = PURE_RECORD_TEXT, PURE_VIEW_BOOL = PURE_RECORD_BOOL, + PURE_VIEW_I64 = PURE_RECORD_I64, PURE_VIEW_LIST = PURE_RECORD_LIST, PURE_VIEW_BYTES = PURE_RECORD_BYTES } PureViewType; typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit; /* EXISTS: an item with that key is already in the keyed list (the event * reports PURE_CONFLICT). MEMORY: the event would leave the states holding @@ -389,8 +395,11 @@ typedef struct { PureField field[PURE_MODULE_FIELDS]; PureValue initial[PURE_MODULE_STATES]; uint8_t view_type[PURE_MODULE_VIEWS]; - uint16_t view_table[PURE_MODULE_VIEWS]; /* a column's table view + 1, or 0 */ - uint16_t view_list[PURE_MODULE_VIEWS]; /* the list of a table or column */ + uint16_t view_table[PURE_MODULE_VIEWS]; /* a column's list view + 1, or 0 */ + uint16_t view_list[PURE_MODULE_VIEWS]; /* the list state of a list view over one, or its column's */ + uint16_t view_record[PURE_MODULE_VIEWS]; /* a list view's or a list column's items' record + 1 (a map's: 0) */ + uint8_t view_field[PURE_MODULE_VIEWS]; /* a column the load made: its item's field + 1 (read as it is), or 0 */ + bool view_computed[PURE_MODULE_VIEWS]; /* a list view whose code leaves its list (a column's: its view's) */ 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 */ @@ -542,7 +551,8 @@ 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, given; size_t *items, count, capacity; PureFault fault; } *rows; /* a filtered table's items */ + /* A filtered list view's items, or a computed one's list (kept: its lists in stored form). */ + struct PureRows { uint32_t table; uint64_t changes, given; size_t *items, count, capacity; PureFault fault; PureList *list; } *rows; unsigned row_tables; /* Current message or text event, kept until pure_app_finish. */ PureSpan payload; @@ -625,10 +635,11 @@ bool pure_app_view(PureApp *, uint32_t view, double *out); /* An i64 view, and an i64 column's cell; false for any other or a failed evaluation. */ bool pure_app_view_i64(PureApp *, uint32_t view, int64_t *out); bool pure_app_cell_i64(PureApp *, uint32_t column, size_t row, int64_t *out); -/* A text view, lent in order until the next view evaluation. */ +/* A text or bytes view, lent in order until the next view evaluation. */ PureResult pure_app_view_text(PureApp *, uint32_t view, bool (*visit)(void *, PureSpan), void *context); -/* 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. */ +/* The rows of a list view (its list's items, those its filter keeps, or + * the items of the list its code leaves), 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. */ @@ -641,13 +652,23 @@ int pure_module_arguments(const PureModule *, uint32_t view); 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: - * its filter leaves the item out). Without a filter both are the identity. */ +/* The item a row of a list view shows, and the row that shows an item + * (false: its filter leaves the item out). Without a filter both are the + * identity. */ bool pure_app_row_item(PureApp *, uint32_t table, size_t row, size_t *item); bool pure_app_item_row(PureApp *, uint32_t table, size_t item, size_t *row); -/* A number or bool column's cell (as pure_app_view), or a text column's. */ +/* A number or bool column's cell (as pure_app_view), or a text or bytes column's. */ bool pure_app_cell(PureApp *, uint32_t column, size_t row, double *out); PureResult pure_app_cell_text(PureApp *, uint32_t column, size_t row, bool (*visit)(void *, PureSpan), void *context); +/* A list column's cell: its items in their stored form (027's stage 3), of + * the column's record (view_record - 1), lent until the next view + * evaluation or change; pure_items_next reads them. */ +PureResult pure_app_cell_items(PureApp *, uint32_t column, size_t row, PureSpan *items); +/* The next item of items (of record's, in stored form) at *at: its cells + * (a text's, a bytes' and a list's in items' bytes, a list's in this form + * again), *at moved past it; false at the end or where the bytes are not + * that form. */ +bool pure_items_next(const PureModule *, uint32_t record, PureSpan items, size_t *at, PureCell *cells); /* Switch to another verified module. States keep their value by name (text * states keep their content, cursors their position in that text); new states * start at their initial value. The module must outlive the app. */ diff --git a/core/test-module.c b/core/test-module.c index a27c0aa..8950018 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -63,9 +63,15 @@ typedef struct { const char *name; uint32_t type, ref; } T; /* type: the state t * a constant as a text's). */ typedef struct { const char *name; uint32_t text; double initial; } RF; typedef struct { const char *name; const RF *fields; size_t nfields; } R; -/* A further view: a table (type 2, start = list state) or a column of the - * view numbered table - 1. */ +/* A further view: a table (type 2, start = list state, or bits 8-15 its + * list + 1 with a filter's block) or a column of the view numbered table - 1 + * (0 a number, 1 a text, 3 a bool, 4 an i64, as views were numbered before + * 027's 4e; 5 bytes); 6 a list view of a record (bits 16-23 its record + 1, + * bits 8-15 its list + 1 or none: its block leaves its list), 7 a list + * column (bits 16-23 its record + 1). Types from 8 on are written as they + * are. */ typedef struct { const char *name; uint32_t type, table, start, length; } W; +enum { W_BYTES = 5, W_OF = 6, W_LIST = 7 }; /* A function (027's 2c): its block, and its parameters' then its results' types (a record field's: 0 a number, 1 a * text, 2 a bool, 3 an i64, 4 a list whose record is beside it; 5 bytes). */ typedef struct { uint32_t start, length; uint8_t params, results, types[16]; uint16_t records[16]; } FN; @@ -289,16 +295,26 @@ static size_t build(unsigned char *out, Spec spec) { for (size_t i = 0; i < spec.nv; ++i) { p = put32(p, offset[n++]); p = put32(p, spec.v[i].length & BOOL_VIEW ? PURE_VIEW_BOOL : spec.v[i].length & I64_VIEW ? PURE_VIEW_I64 : 0); + p = put32(p, 0); p = put32(p, spec.v[i].start); p = put32(p, spec.v[i].length & ~(BOOL_VIEW | I64_VIEW)); } - for (size_t i = 0; i < spec.ntv; ++i) { p = put32(p, offset[n++]); p = put32(p, 1); p = put32(p, spec.tv[i].start); p = put32(p, spec.tv[i].length); } - for (size_t i = 0; i < spec.nw; ++i) { + for (size_t i = 0; i < spec.ntv; ++i) { + p = put32(p, offset[n++]); p = put32(p, PURE_VIEW_TEXT); p = put32(p, 0); + p = put32(p, spec.tv[i].start); p = put32(p, spec.tv[i].length); + } + static const uint8_t w_types[] = {PURE_VIEW_NUM, PURE_VIEW_TEXT, PURE_VIEW_LIST, PURE_VIEW_BOOL, PURE_VIEW_I64, + PURE_VIEW_BYTES, PURE_VIEW_LIST, PURE_VIEW_LIST}; + for (size_t i = 0; i < spec.nw; ++i) { /* 20 bytes (027's 4e): type, 0, table + 1, record + 1, list + 1, block */ const W *w = &spec.w[i]; + uint32_t code = w->type & 0xFF, list = w->type >> 8 & 0xFF, record = w->type >> 16 & 0xFF, start = w->start; + if (code == 2 && !list) list = start + 1, start = 0; /* a table without a filter named its list in start */ p = put32(p, offset[n++]); - *p++ = (unsigned char)w->type; *p++ = (unsigned char)(w->type >> 8); /* a filtered table: its list + 1 */ + *p++ = code < sizeof(w_types) ? w_types[code] : (unsigned char)code; *p++ = 0; *p++ = (unsigned char)w->table; *p++ = (unsigned char)(w->table >> 8); - p = put32(p, w->start); + *p++ = (unsigned char)record; *p++ = 0; + *p++ = (unsigned char)list; *p++ = 0; + p = put32(p, start); p = put32(p, w->length); } for (size_t i = 0; i < spec.nh; ++i) { p = put32(p, spec.h[i].event); p = put32(p, spec.h[i].start); p = put32(p, spec.h[i].length); } @@ -2320,7 +2336,7 @@ static void tables(void) { static const H handlers[] = {{1, 0, 5}, {3, 5, 5}}; static const V text_views[] = {{"current", 10, 8}}; static const W table[] = {{"board", 2, 0, 1, 0}, {"title", 1, 2, 18, 2}, {"mark", 1, 2, 20, 6}, - {"pos", 0, 2, 26, 2}, {"here", PURE_VIEW_BOOL, 2, 28, 5}}; + {"pos", 0, 2, 26, 2}, {"here", 3, 2, 28, 5}}; Spec spec = {nums, 1, inputs, 2, NULL, 0, handlers, 2, code, LEN(code), NULL, 0, states, 2, constants, 4, text_views, 1, records, 1, table, 5}; table_spec = spec; @@ -2367,7 +2383,7 @@ static void tables(void) { CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view type")); static const W over_number[] = {{"board", 2, 0, 0, 0}}; bad = spec; bad.w = over_number; bad.nw = 1; - CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "table list")); + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "list view's list")); static const O other_row[] = {{ROW, 2, 0}, {XN, 2, 0}}; static const W other_row_column[] = {{"board", 2, 0, 1, 0}, {"pos", 0, 1, 0, 2}}; bad = spec; bad.o = other_row; bad.no = 2; bad.h = NULL; bad.nh = 0; bad.tv = NULL; bad.ntv = 0; @@ -3202,7 +3218,7 @@ static void filters(void) { static const O text_filter[] = {{ROW, 0, 0}, {IG, F_TITLE, 0}}; static const O number_filter[] = {{ROW, 0, 0}, {IG, F_POINTS, 0}}; static const O folded[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 1, 1}}; - static const W filtered_by[] = {{"open", FILTERED, 0, 0, 0}}, open3[] = {{"open", FILTERED, 0, 0, 3}}, + static const W filtered_by[] = {{"open", W_OF | 1 << 16, 0, 0, 0}}, open3[] = {{"open", FILTERED, 0, 0, 3}}, open2[] = {{"open", FILTERED, 0, 0, 2}}; static const W not_a_list[] = {{"open", 2 | 2 << 8, 0, 0, 3}}; static const W flagged_column[] = {{"open", FILTERED, 0, 0, 3}, {"c", 0 | 1 << 8, 1, 0, 3}}; @@ -3211,8 +3227,8 @@ static void filters(void) { {text_filter, LEN(text_filter), open2, 1, "filter must leave one bool"}, {number_filter, LEN(number_filter), open2, 1, "filter must leave one bool"}, {folded, LEN(folded), open2, 1, "fold inside a filter"}, - {effect, LEN(effect), filtered_by, 1, "table list"}, /* a filtered table needs its block */ - {effect, LEN(effect), not_a_list, 1, "table list"}, /* state 1 is an index */ + {effect, LEN(effect), filtered_by, 1, "list view's list"}, /* a computed list view needs its block */ + {effect, LEN(effect), not_a_list, 1, "list view's list"}, /* state 1 is an index */ {effect, LEN(effect), flagged_column, 2, "view type"}, /* only a table has a list byte */ }; for (size_t i = 0; i < LEN(refused); ++i) @@ -4146,7 +4162,7 @@ static void fold_mutations(void) { for (uint32_t v = 0; v < module_b.views; ++v) { double value; size_t item; - if (module_b.view_type[v] == PURE_VIEW_TABLE) { + if (module_b.view_type[v] == PURE_VIEW_LIST) { size_t rows = pure_app_rows(&app, v); for (size_t row = 0; row < rows + 1; ++row) pure_app_row_item(&app, v, row, &item); } else if (module_b.view_table[v]) { @@ -4464,7 +4480,7 @@ static void describe(PureApp *app, Shown *s) { } for (uint32_t v = 0; v < m->views; ++v) { double value; - if (m->view_table[v] || m->view_type[v] == PURE_VIEW_TABLE) continue; + if (m->view_table[v] || m->view_type[v] == PURE_VIEW_LIST) continue; if (m->view_type[v] == PURE_VIEW_TEXT) { if (pure_app_view_text(app, v, show_span, s) != PURE_OK) show(s, "!", 0); } else if (pure_app_view(app, v, &value)) show(s, "%g", value); @@ -5628,7 +5644,7 @@ static void bools(void) { static const RF two_default[] = {{"done", 2, 2}}; static const R two_record[] = {{"r", two_default, 1}}; static const T with_list[] = {{"on", 5, 1}, {"off", 5, 0}, {"rs", 3, 0}}; - static const W fifth_view[] = {{"x", 5, 0, 25, 1}}; + static const W fifth_view[] = {{"x", 8, 0, 25, 1}}; bad = spec; bad.t = two_initial; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "bool initial value")); @@ -6713,6 +6729,107 @@ static void anchors(void) { printf("{\"anchors\":{\"views\":6,\"refused\":%zu}}\n", LEN(refused) + 1); } +/* List views (027's 4e): a list state as it is and filtered (its record's fields its columns, made at load, a list + * field's cell lent in its stored form), a list a view's body builds (kept for its rows and cells until a change), a + * map's list and bytes columns, a bytes view; refused: a computed view without its block, a record not its list's, a + * column of a view of a record, a list column without its record, a body leaving a list of another record, a pointing + * at a computed list. */ +static bool tags_are(PureApp *app, uint32_t column, size_t row, const int64_t *want, size_t count) { + PureSpan items; + PureCell cells[PURE_RECORD_FIELDS]; + if (pure_app_cell_items(app, column, row, &items) != PURE_OK) return false; + size_t at = 0, seen = 0; + while (pure_items_next(app->module, 0, items, &at, cells)) + if (seen >= count || cells[0].i64 != want[seen++]) return false; + return seen == count && at == items.size; +} + +static void list_views(void) { + enum { BF = PURE_OP_BYTES_FROM, TB = PURE_OP_TEXT_BYTES, ITEMS = 1, TAGS = 2, MADE = 3 }; + enum { ALL, LONG, COMPUTED, MAP, MAP_TAGS, MAP_RAW, BLOB }; + static const S nums[] = {{"n", 0}}; + static const char *const constants[] = {"", "a", "zz", "bb"}; + static const RF tag_fields[] = {{"v", 3, 0}}; + static const RF item_fields[] = {{"name", 1, 0}, {"raw", 5, 0}, {"tags", 4, 0}}; + static const R records[] = {{"Tag", tag_fields, 1}, {"Item", item_fields, 3}}; + static const T others[] = {{"items", 3, 1}, {"let.tags", 3 | (uint32_t)PURE_LIST_ROWS << 8, 0}, + {"let.made", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}}; + static const I inputs[] = {KEY("go", "KeyG")}; + const O code[] = { + IC(7), {LA, TAGS, 0}, IC(9), {LA, TAGS, 0}, /* 0 tags = [7, 9] */ + {TK, 1, 0}, {TK, 2, 0}, {BF, 0, 0}, {LV, TAGS, 0}, {LA, ITEMS, 0}, /* 4 items += { a, zz, tags } */ + IC(11), {LA, TAGS, 0}, /* 9 tags += 11 */ + {TK, 3, 0}, {TK, 0, 0}, {BF, 0, 0}, {LV, TAGS, 0}, {LA, ITEMS, 0}, /* 11 items += { bb, "", tags } */ + {LC, ITEMS, 0}, {ST, 0, 0}, /* 16 n = items.count */ + {ROW, ITEMS, 0}, {IG, ITEM(ITEMS, 0), 0}, {TB, 0, 0}, {C, 0, 1}, {GT, 0, 0}, /* 18 long: a name past a byte */ + IC(5), {LA, TAGS, 0}, {TK, 1, 0}, {TK, 2, 0}, {BF, 0, 0}, {LV, TAGS, 0}, /* 23 computed: [{ a, zz, [5] }] */ + {LA, MADE, 0}, {LV, MADE, 0}, + {ROW, ITEMS, 0}, {IG, ITEM(ITEMS, 2), 0}, /* 31 map's tags */ + {ROW, ITEMS, 0}, {IG, ITEM(ITEMS, 1), 0}, /* 33 map's raw */ + {TK, 2, 0}, {BF, 0, 0}, /* 35 blob */ + }; + static const W views[] = {{"all", W_OF | 2 << 16 | (ITEMS + 1) << 8, 0, 0, 0}, {"long", W_OF | 2 << 16 | (ITEMS + 1) << 8, 0, 18, 5}, + {"computed", W_OF | 2 << 16, 0, 23, 8}, {"map", 2, 0, ITEMS, 0}, + {"tags", W_LIST | 1 << 16, MAP + 1, 31, 2}, {"raw", W_BYTES, MAP + 1, 33, 2}, + {"blob", W_BYTES, 0, 35, 2}}; + H handler = {1, 0, 18}; + Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, code, LEN(code), NULL, 0, others, LEN(others), constants, + LEN(constants), NULL, 0, records, 2, views, LEN(views)}; + if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "list views: %s\n", error); abort(); } + const PureModule *m = &module_a; + int name = pure_module_column(m, ALL, "name"), raw = pure_module_column(m, ALL, "raw"), tags = pure_module_column(m, ALL, "tags"); + int long_name = pure_module_column(m, LONG, "name"), long_tags = pure_module_column(m, LONG, "tags"); + int made_name = pure_module_column(m, COMPUTED, "name"), made_tags = pure_module_column(m, COMPUTED, "tags"); + CHECK(name >= (int)LEN(views) && raw > name && tags > raw && long_tags > long_name && made_tags > made_name); + CHECK(m->view_type[name] == PURE_VIEW_TEXT && m->view_type[raw] == PURE_VIEW_BYTES && m->view_type[tags] == PURE_VIEW_LIST && + m->view_record[tags] == 1 && m->view_type[COMPUTED] == PURE_VIEW_LIST && m->view_record[COMPUTED] == 2); + PureApp app; + CHECK(pure_app_start(&app, m, (PureData){0}) == PURE_OK); + CHECK(pure_app_rows(&app, ALL) == 0 && pure_app_rows(&app, COMPUTED) == 1); + CHECK(press(&app, 0) == PURE_OK && app.values[0].num == 2); + static const int64_t two[] = {7, 9}, three[] = {7, 9, 11}, five[] = {5}; + CHECK(pure_app_rows(&app, ALL) == 2 && !strcmp(cell_text(&app, (uint32_t)name, 1), "bb") && + !strcmp(cell_text(&app, (uint32_t)raw, 0), "zz") && tags_are(&app, (uint32_t)tags, 0, two, 2) && + tags_are(&app, (uint32_t)tags, 1, three, 3)); + CHECK(pure_app_rows(&app, LONG) == 1 && !strcmp(cell_text(&app, (uint32_t)long_name, 0), "bb") && + tags_are(&app, (uint32_t)long_tags, 0, three, 3)); + CHECK(pure_app_rows(&app, COMPUTED) == 1 && !strcmp(view_text(&app, BLOB), "zz") && /* another evaluation between */ + !strcmp(cell_text(&app, (uint32_t)made_name, 0), "a") && tags_are(&app, (uint32_t)made_tags, 0, five, 1)); + CHECK(pure_app_rows(&app, MAP) == 2 && tags_are(&app, MAP_TAGS, 0, two, 2) && tags_are(&app, MAP_TAGS, 1, three, 3) && + !strcmp(cell_text(&app, MAP_RAW, 0), "zz") && !strcmp(cell_text(&app, MAP_RAW, 1), "")); + double number; + CHECK(!pure_app_cell(&app, (uint32_t)name, 0, &number) && !pure_app_cell(&app, (uint32_t)name, 2, &number)); + CHECK(press(&app, 0) == PURE_OK && pure_app_rows(&app, ALL) == 4 && pure_app_rows(&app, LONG) == 2 && + pure_app_rows(&app, COMPUTED) == 1); + size_t item; + CHECK(pure_app_row_item(&app, LONG, 1, &item) && item == 3 && pure_app_row_item(&app, COMPUTED, 0, &item) && item == 0 && + !pure_app_row_item(&app, COMPUTED, 1, &item)); + pure_app_stop(&app); + static const W no_block[] = {{"x", W_OF | 2 << 16, 0, 0, 0}}, not_its_record[] = {{"x", W_OF | 1 << 16 | (ITEMS + 1) << 8, 0, 0, 0}}, + made_column[] = {{"all", W_OF | 2 << 16 | (ITEMS + 1) << 8, 0, 0, 0}, {"c", 0, ALL + 1, 18, 2}}, + unrecorded[] = {{"map", 2, 0, ITEMS, 0}, {"tags", W_LIST, 1, 31, 2}}, + another_record[] = {{"x", W_OF | 1 << 16, 0, 23, 8}}, + pointed[] = {{"computed", W_OF | 2 << 16, 0, 23, 8}}; + static const I pointing_at[] = {KEY("go", "KeyG"), {"computed.point", "0", POINT_INPUT, NULL, 0}}; + const struct { const W *w; size_t nw; const I *in; size_t ni; const char *why; } refused[] = { + {no_block, 1, inputs, 1, "list view's list"}, + {not_its_record, 1, inputs, 1, "list view's list"}, + {made_column, 2, inputs, 1, "view type"}, + {unrecorded, 2, inputs, 1, "view type"}, + {another_record, 1, inputs, 1, "view must leave one value of its type"}, + {pointed, 1, pointing_at, 2, "pointing's table"}, + }; + for (size_t k = 0; k < LEN(refused); ++k) { + Spec bad = spec; + bad.w = refused[k].w, bad.nw = refused[k].nw, bad.in = refused[k].in, bad.ni = refused[k].ni; + if (load(bad, &module_b) == PURE_OK || !strstr(error, refused[k].why)) { + fprintf(stderr, "list views: refused %zu: %s\n", k, error); + abort(); + } + } + printf("{\"list_views\":{\"views\":%zu,\"refused\":%zu}}\n", LEN(views), LEN(refused)); +} + /* A timer's tick sees the time the host set; floor and a floored modulo * take it apart. now is for handlers only. */ enum { NOW = PURE_OP_NOW, FLOOR = PURE_OP_FLOOR, MOD = PURE_OP_MOD }; @@ -7095,6 +7212,7 @@ int main(void) { nested_rows(); bytes_fields(); anchors(); + list_views(); memory_budget(); work_budget(); fold_mutations(); |
