aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-26 11:28:13 +0100
committereunhhu <[email protected]>2026-09-26 11:28:13 +0100
commitee0791397be3dc6d5b56611b505a312f1d8350a0 (patch)
tree6fd4400c27731d053b096c527eda683cc93eb0e2 /prototypes/connections
parentcd3fcb9873e23c2d07bd4e9d02d2111c96959440 (diff)
downloadpure-ee0791397be3dc6d5b56611b505a312f1d8350a0.tar.gz
pure-ee0791397be3dc6d5b56611b505a312f1d8350a0.zip
Filter a table's rows
A table view can now carry a filter: its list goes in the byte after the view's type (list + 1) and its code range is a block run as a column of the list, leaving one number; the table has a row for each item the block keeps, in the list's order. Columns read the item (ROW is the item's position, not the row's). The filter runs once per item after a change of the app's states, counted by a new number that never goes back (events, points, a failed save's undo, a module replacement), and its rows serve every cell until the next change. pure_app_row_item and pure_app_item_row turn a table's rows into items and back, so a host shows a focus (an index names an item) on the row it is in and points at the item a clicked row shows. pure_app_rows now takes a mutable app. The checks cover rows, cells, the mapping both ways as items leave and come back, six refusals, and 20,000 damaged filter modules.
Diffstat (limited to 'prototypes/connections')
-rw-r--r--prototypes/connections/module.c106
-rw-r--r--prototypes/connections/module.h18
-rw-r--r--prototypes/connections/test-module.c96
3 files changed, 195 insertions, 25 deletions
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c
index 6a373a3..ffdcaf7 100644
--- a/prototypes/connections/module.c
+++ b/prototypes/connections/module.c
@@ -22,8 +22,9 @@ enum { READ = IN_VIEW | IN_HANDLER };
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 fold's block is verified as a column that leaves one number (count) or
- * two (a value and whether to include it): these stand for a view type. */
-enum { FOLD_ONE = 100, FOLD_TWO };
+ * two (a value and whether to include it), a table's filter as a column that
+ * leaves one: these stand for a view type. */
+enum { FOLD_ONE = 100, FOLD_TWO, FILTER };
/* 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_OPERAND:
* it pushes a position in its operand's text or list. O_ARG: the argument.
@@ -237,6 +238,7 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block,
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 (view == FILTER) REFUSE("filter with an effect");
if (view >= FOLD_ONE) REFUSE("fold with an effect");
if (view >= 0) REFUSE("view with an effect");
if (allowed & IN_KEYED) REFUSE("document operation outside a keyed message handler");
@@ -262,7 +264,7 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block,
if (kind == A_FOLD) {
uint32_t inner = pc;
skip = arg >> 16;
- if (view >= FOLD_ONE) REFUSE("fold inside a fold");
+ if (view >= FOLD_ONE) REFUSE(view == FILTER ? "fold inside a filter" : "fold inside a fold");
if (skip > block.length - i - 1) REFUSE("fold block outside its block");
const char *why = verify_block(m, (PureBlock){pc + 1, skip}, (arg & 0xFF) == PURE_FOLD_COUNT ? FOLD_ONE : FOLD_TWO,
(int)(arg >> 8 & 0xFF), 0, &inner);
@@ -322,6 +324,7 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block,
i += skip;
}
if (!reachable) return "block end is unreachable";
+ if (view == FILTER) return height != 1 || types[0] != T_NUM ? "filter must leave one number" : NULL;
if (view >= FOLD_ONE) {
int want = view - FOLD_ONE + 1;
return height != want || types[0] != T_NUM || types[want - 1] != T_NUM ? "fold block must leave its numbers" : NULL;
@@ -484,13 +487,15 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
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_TABLE || at[5] || (table && (table > i || out->view_type[table - 1] != PURE_VIEW_TABLE ||
- at[4] == PURE_VIEW_TABLE)))
+ if (at[4] > PURE_VIEW_TABLE || (at[5] && at[4] != PURE_VIEW_TABLE) ||
+ (table && (table > i || out->view_type[table - 1] != PURE_VIEW_TABLE || at[4] == PURE_VIEW_TABLE)))
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) {
- if (!state_of(out, out->view_code[i].start, PURE_STATE_LIST) || out->view_code[i].length)
- REFUSE("table list", i);
- out->view_list[i] = (uint16_t)out->view_code[i].start;
+ 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;
} else if (table) out->view_list[i] = out->view_list[table - 1];
}
for (uint32_t i = 0; i < out->handlers; ++i, at += HANDLER_BYTES) {
@@ -523,9 +528,10 @@ 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;
- if (out->view_type[i] != PURE_VIEW_TABLE &&
- (why = verify_block(out, out->view_code[i], out->view_type[i], out->view_table[i] ? out->view_list[i] : -1, 0,
- &bad))) goto refused;
+ 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;
}
for (uint32_t i = 0; i < out->handlers; ++i) {
bad = i;
@@ -1073,6 +1079,7 @@ static void close_burst(PureApp *app, bool keep) {
memcpy(app->values, app->durable_values, sizeof(app->values));
app->ticks = app->durable_ticks;
app->events = app->durable_events;
+ ++app->changes;
}
app->open = false;
}
@@ -1105,6 +1112,8 @@ void pure_app_stop(PureApp *app) {
pure_draft_close(app->stored_values);
free(app->pending);
pure_undo_free(&app->undo);
+ for (unsigned i = 0; i < app->row_tables; ++i) free(app->rows[i].items);
+ free(app->rows);
free(app->arena.bytes);
free(app->view_arena.bytes);
memset(app, 0, sizeof(*app));
@@ -1162,6 +1171,7 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) {
}
memcpy(app->values, pending, sizeof(pending));
++app->events;
+ ++app->changes;
if (!event) ++app->ticks;
if (app->document) {
app->pending[app->pending_count].document = app->document;
@@ -1196,6 +1206,7 @@ PureResult pure_app_point(PureApp *app, uint32_t state, size_t position) {
app->fault_pc = UINT32_MAX;
app->values[state] = (double)position;
++app->events;
+ ++app->changes;
return app->deferred ? PURE_OK : pure_app_save(app);
}
@@ -1280,11 +1291,76 @@ static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) {
return result;
}
+/* 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. */
+static struct PureRows *filtered(PureApp *app, uint32_t table) {
+ const PureModule *m = app->module;
+ if (!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) {
+ struct PureRows *grown = realloc(app->rows, (app->row_tables + 1) * sizeof(*grown));
+ if (!grown) return NULL;
+ app->rows = grown;
+ rows = &app->rows[app->row_tables++];
+ *rows = (struct PureRows){.table = table, .changes = UINT64_MAX};
+ }
+ if (rows->changes == app->changes) return rows;
+ rows->count = 0;
+ for (size_t item = 0, items = pure_list_count(app->lists[m->view_list[table]]); item < items; ++item) {
+ Slot keep;
+ if (evaluate(app, table, item, &keep) != PURE_OK || keep.num == 0) continue;
+ if (rows->count == rows->capacity) {
+ size_t capacity = rows->capacity ? 2 * rows->capacity : 16;
+ size_t *grown = realloc(rows->items, capacity * sizeof(*grown));
+ if (!grown) return NULL;
+ rows->items = grown;
+ rows->capacity = capacity;
+ }
+ rows->items[rows->count++] = item;
+ }
+ rows->changes = app->changes;
+ return rows;
+}
+
+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) {
+ *item = row;
+ return row < pure_list_count(app->lists[m->view_list[table]]);
+ }
+ struct PureRows *rows = filtered(app, table);
+ if (!rows || row >= rows->count) return false;
+ *item = rows->items[row];
+ return true;
+}
+
+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 (!m->view_code[table].length) {
+ *row = item;
+ return item < pure_list_count(app->lists[m->view_list[table]]);
+ }
+ struct PureRows *rows = filtered(app, table);
+ size_t low = 0, high = rows ? rows->count : 0; /* the items are in order */
+ while (low < high) {
+ size_t middle = low + (high - low) / 2;
+ if (rows->items[middle] < item) low = middle + 1;
+ else high = middle;
+ }
+ if (low == (rows ? rows->count : 0) || rows->items[low] != item) return false;
+ *row = low;
+ 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) {
const PureModule *m = app->module;
if (view >= m->views || m->view_type[view] != type || (m->view_table[view] != 0) != column) return PURE_INVALID;
- if (column && row >= pure_list_count(app->lists[m->view_list[view]])) return PURE_NOT_FOUND;
+ if (column && !pure_app_row_item(app, m->view_table[view] - 1u, row, &row)) return PURE_NOT_FOUND;
return evaluate(app, view, row, slot);
}
@@ -1301,10 +1377,11 @@ PureResult pure_app_view_text(PureApp *app, uint32_t view, bool (*visit)(void *,
return result == PURE_OK ? visit_value(app, &app->view_arena, slot.bytes, visit, context) : result;
}
-size_t pure_app_rows(const PureApp *app, uint32_t table) {
+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;
- return pure_list_count(app->lists[m->view_list[table]]);
+ struct PureRows *rows = filtered(app, table);
+ return rows ? rows->count : 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) {
@@ -1456,6 +1533,7 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module)
memcpy(app->list_drafts, list_drafts, sizeof(list_drafts));
memcpy(app->saved, saved, sizeof(saved));
app->module = module;
+ ++app->changes;
return PURE_OK;
}
diff --git a/prototypes/connections/module.h b/prototypes/connections/module.h
index 6938350..b57e11f 100644
--- a/prototypes/connections/module.h
+++ b/prototypes/connections/module.h
@@ -54,8 +54,9 @@ typedef enum {
/* A cursor that stays before text inserted at it (a mark); other cursors go
* after it (a caret). */
enum { PURE_CURSOR_STAYS = 1 };
-/* A table view has one row per item of a list; its columns are number or
- * text views evaluated for one row at a time. */
+/* A table view has one row per item of a list, or per item its filter
+ * keeps; its columns are number or text views evaluated for one row at a
+ * time. */
typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2 } PureViewType;
typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit;
typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge;
@@ -181,6 +182,9 @@ typedef struct {
PureFault fault;
uint32_t fault_pc;
uint64_t ticks, events;
+ uint64_t changes; /* counts every change of states, never back: what a filter's rows are fresh for */
+ struct PureRows { uint32_t table; uint64_t changes; size_t *items, count, capacity; } *rows; /* a filtered table's items */
+ unsigned row_tables;
/* Current message or text event, kept until pure_app_finish. */
void *document;
PureSpan payload;
@@ -220,9 +224,13 @@ void pure_app_finish(PureApp *);
bool pure_app_view(PureApp *, uint32_t view, double *out);
/* A text 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 0 for another view. A
- * host evaluates only the cells it shows. */
-size_t pure_app_rows(const PureApp *, uint32_t table);
+/* The rows of a table view (its list's items, or those its filter keeps),
+ * or 0 for another view. A host evaluates only the cells it shows. */
+size_t pure_app_rows(PureApp *, uint32_t table);
+/* 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. */
+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);
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);
/* Switch to another verified module. States keep their value by name (text
diff --git a/prototypes/connections/test-module.c b/prototypes/connections/test-module.c
index 918978e..6b11c3f 100644
--- a/prototypes/connections/test-module.c
+++ b/prototypes/connections/test-module.c
@@ -155,7 +155,8 @@ static size_t build(unsigned char *out, Spec spec) {
for (size_t i = 0; i < spec.nw; ++i) {
const W *w = &spec.w[i];
p = put32(p, offset[n++]);
- *p++ = (unsigned char)w->type; *p++ = 0; *p++ = (unsigned char)w->table; *p++ = (unsigned char)(w->table >> 8);
+ *p++ = (unsigned char)w->type; *p++ = (unsigned char)(w->type >> 8); /* a filtered table: its list + 1 */
+ *p++ = (unsigned char)w->table; *p++ = (unsigned char)(w->table >> 8);
p = put32(p, w->start);
p = put32(p, w->length);
}
@@ -1834,11 +1835,84 @@ static void folds(void) {
}
}
-/* Damaged fold modules: whatever loads runs safely. */
+/* A filtered table: one row per item its filter keeps; its columns read the
+ * item (ROW is the item, not the row), and hosts turn rows into items and
+ * back. The rows follow every change. */
+static const O filter_code[] = {
+ {TI, 0, 0}, {C, 0, 0}, {TI, 0, 0}, {CHR, 0, 0}, {LA, 0, 0}, {XE, 0, 0}, {XS, 1, 0}, /* 0 typed */
+ {XL, 1, 0}, {XL, 1, 0}, {IG, F_DONE, 0}, {NOT, 0, 0}, {IS, F_DONE, 0}, /* 7 toggle */
+ {ROW, 0, 0}, {IG, F_DONE, 0}, {NOT, 0, 0}, /* 12 filter: open */
+ {ROW, 0, 0}, {IG, F_TITLE, 0}, /* 15 title */
+ {ROW, 0, 0}, {XN, 0, 0}, /* 17 pos */
+};
+enum { FILTERED = 2 | 1 << 8 }; /* a table over state 0, filtered */
+static const W filter_views[] = {{"open", FILTERED, 0, 12, 3}, {"title", 1, 1, 15, 2}, {"pos", 0, 1, 17, 2}};
+
+static Spec filter_spec(const O *code, size_t count, const W *views, size_t nviews) {
+ return (Spec){NULL, 0, fold_inputs, LEN(fold_inputs), NULL, 0, fold_handlers, 2, code, count, NULL, 0,
+ fold_states, 2, fold_constants, 1, NULL, 0, fold_records, 1, views, nviews};
+}
+
+static const char *cell_title(PureApp *app, uint32_t column, size_t row) {
+ static Text text;
+ text = (Text){{0}, 0};
+ CHECK(pure_app_cell_text(app, column, row, text_span, &text) == PURE_OK && text.size < sizeof(text.text));
+ text.text[text.size] = 0;
+ return text.text;
+}
+
+static void filters(void) {
+ CHECK(load(filter_spec(filter_code, LEN(filter_code), filter_views, 3), &module_a) == PURE_OK);
+ PureApp app;
+ double value;
+ size_t at;
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK && pure_app_rows(&app, 0) == 0);
+ for (const char *word = "abc"; *word; ++word)
+ CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK);
+ CHECK(pure_app_rows(&app, 0) == 3 && !strcmp(cell_title(&app, 1, 2), "c") && pure_app_cell(&app, 2, 2, &value) && value == 2);
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* c is done: it leaves */
+ CHECK(pure_app_rows(&app, 0) == 2 && !pure_app_cell(&app, 2, 2, &value) && !pure_app_item_row(&app, 0, 2, &at));
+ CHECK(pure_app_point(&app, 1, 0) == PURE_OK && pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* a too */
+ CHECK(pure_app_rows(&app, 0) == 1 && !strcmp(cell_title(&app, 1, 0), "b"));
+ CHECK(pure_app_cell(&app, 2, 0, &value) && value == 1); /* the item's position, not the row's */
+ CHECK(pure_app_row_item(&app, 0, 0, &at) && at == 1 && !pure_app_row_item(&app, 0, 1, &at));
+ CHECK(pure_app_item_row(&app, 0, 1, &at) && at == 0 && !pure_app_item_row(&app, 0, 0, &at));
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* a comes back, before b */
+ CHECK(pure_app_rows(&app, 0) == 2 && !strcmp(cell_title(&app, 1, 0), "a") && pure_app_item_row(&app, 0, 1, &at) && at == 1);
+ CHECK(pure_app_replace(&app, &module_a) == PURE_OK && pure_app_rows(&app, 0) == 2);
+ pure_app_stop(&app);
+
+ /* What a filter may not be. */
+ static const O effect[] = {{ROW, 0, 0}, {XS, 1, 0}, {C, 0, 1}};
+ static const O text_filter[] = {{ROW, 0, 0}, {IG, F_TITLE, 0}};
+ static const O folded[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 0, 1}};
+ static const W filtered_by[] = {{"open", FILTERED, 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}};
+ static const struct { const O *code; size_t count; const W *views; size_t nviews; const char *reason; } refused[] = {
+ {effect, LEN(effect), open3, 1, "filter with an effect"},
+ {text_filter, LEN(text_filter), open2, 1, "filter must leave one number"},
+ {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), flagged_column, 2, "view type"}, /* only a table has a list byte */
+ };
+ for (size_t i = 0; i < LEN(refused); ++i)
+ if (load(filter_spec(refused[i].code, refused[i].count, refused[i].views, refused[i].nviews), &module_b) != PURE_INVALID ||
+ !strstr(error, refused[i].reason)) {
+ fprintf(stderr, "filter case %zu: expected '%s', got '%s'\n", i, refused[i].reason, error);
+ abort();
+ }
+}
+
+/* Damaged fold and filter modules: whatever loads runs safely. */
static void fold_mutations(void) {
static unsigned char valid[65536], bytes[65536];
size_t size = build(valid, fold_spec(fold_code, LEN(fold_code), fold_views, LEN(fold_views), fold_handlers, 2));
- uint32_t state = 0x27d4eb2fu;
+ for (unsigned kind = 0; kind < 2; ++kind) {
+ if (kind) size = build(valid, filter_spec(filter_code, LEN(filter_code), filter_views, 3));
+ uint32_t state = kind ? 0x165667b1u : 0x27d4eb2fu;
unsigned accepted = 0;
for (unsigned round = 0; round < 20000; ++round) {
memcpy(bytes, valid, size);
@@ -1854,13 +1928,22 @@ static void fold_mutations(void) {
else if (module_b.input[input].kind == PURE_KIND_KEY) pure_app_input(&app, input, PURE_EDGE_DOWN);
for (uint32_t v = 0; v < module_b.views; ++v) {
double value;
- if (module_b.view_type[v] == PURE_VIEW_TEXT) pure_app_view_text(&app, v, ignore_span, NULL);
+ size_t item;
+ if (module_b.view_type[v] == PURE_VIEW_TABLE) {
+ 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]) {
+ for (size_t row = 0; row < 4; ++row)
+ if (module_b.view_type[v] == PURE_VIEW_TEXT) pure_app_cell_text(&app, v, row, ignore_span, NULL);
+ else pure_app_cell(&app, v, row, &value);
+ } else if (module_b.view_type[v] == PURE_VIEW_TEXT) pure_app_view_text(&app, v, ignore_span, NULL);
else pure_app_view(&app, v, &value);
}
}
pure_app_stop(&app);
}
- printf("{\"fold_module_mutations\":20000,\"accepted_after_damage\":%u}\n", accepted);
+ printf("{\"%s_module_mutations\":20000,\"accepted_after_damage\":%u}\n", kind ? "filter" : "fold", accepted);
+ }
}
/* A host points at an item: the index a view shows as its position moves
@@ -1904,6 +1987,7 @@ int main(void) {
text_mutations();
lists();
folds();
+ filters();
fold_mutations();
pointing();
list_mutations();
@@ -1912,6 +1996,6 @@ int main(void) {
stored_states();
deferred();
printf("{\"sizeof_module\":%zu,\"sizeof_app\":%zu}\n", sizeof(PureModule), sizeof(PureApp));
- puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing-marks-folds\"}");
+ puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing-marks-folds-filters\"}");
return 0;
}