aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 22:11:12 +0100
committereunhhu <[email protected]>2026-10-18 22:11:12 +0100
commita72a1a7acdf6c5ecc4a6668f6b9a3af16ce48555 (patch)
tree1586219b4b49893faab58171292d8697cab1e153 /core
parentda0d7514b058f1baa99a8bcc8f9e23b43cac12de (diff)
downloadpure-a72a1a7acdf6c5ecc4a6668f6b9a3af16ce48555.tar.gz
pure-a72a1a7acdf6c5ecc4a6668f6b9a3af16ce48555.zip
Module format v7, 027's 4e: a view of any value: a view's and a column's type a record field's (number, text, bool, i64, list, bytes; PURE_VIEW_TABLE now PURE_VIEW_LIST), a view entry 20 bytes (its table + 1, its items' record + 1, its list state + 1); a list view's rows a list state's items as they are or filtered (its record's fields its columns, made at load: each reads its item's field as it is), a list state's map (its columns following it, as tables were), or the items of a list its block leaves (copied into a kept list once a change, its rows and cells kept until the next); a list cell lent in its items' stored form (pure_app_cell_items, pure_items_next reading it), bytes as a text is; a pointing only at a list view over a state; pure-run writes bytes as hex and a list cell as an array of objects, pure-serve a bytes view as application/octet-stream and, in JSON, bytes as hex strings and list cells as arrays of objects; a hole closed: a view without a body never began its growth anew, so in a snapshot's reader (pure-serve's views) reading a kept list's list failed as memory (unreachable before 4e: no such view could be written), now marked a body at load; unit test list_views (as it is, filtered, computed, a map's list and bytes columns, a bytes view, six refused); check server_trees keeps its forests in a stored list shown by list views over HTTP against the model, a restart too; counts: the first build's bank tables rose (field +5.9%: view_of left inline at its seventh caller, serve's put_bytes too), so view_of is inlined, the made columns' and computed lists' paths and serve's new writers kept apart, a computed list's rows held as a filter's; b37 against b35 within 0.001% with batches and without; the bank's queries (s35 on the 4d aup's module, s37 on 4e's, six each in turns) field +0.67%, found +0.43%, large +0.31%, the rest within 0.20%; 008 and 027
Diffstat (limited to 'core')
-rw-r--r--core/module.c217
-rw-r--r--core/module.h53
-rw-r--r--core/test-module.c146
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();