diff options
| author | eunhhu <[email protected]> | 2026-09-26 11:28:13 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-26 11:28:13 +0100 |
| commit | ee0791397be3dc6d5b56611b505a312f1d8350a0 (patch) | |
| tree | 6fd4400c27731d053b096c527eda683cc93eb0e2 /prototypes/connections | |
| parent | cd3fcb9873e23c2d07bd4e9d02d2111c96959440 (diff) | |
| download | pure-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.c | 106 | ||||
| -rw-r--r-- | prototypes/connections/module.h | 18 | ||||
| -rw-r--r-- | prototypes/connections/test-module.c | 96 |
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; } |
