aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/design/008-module-format.md10
-rw-r--r--prototypes/connections/module.c106
-rw-r--r--prototypes/connections/module.h18
-rw-r--r--prototypes/connections/test-module.c96
4 files changed, 203 insertions, 27 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index 761077a..456f6c8 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -32,7 +32,7 @@ v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마�
| | 상수 × 8 B: u32 블롭 오프셋, u32 길이 (UTF-8) |
| | 레코드 × 8 B: u32 이름, u16 첫 레코드 필드, u16 필드 수(1–16) |
| | 레코드 필드 × 16 B: u32 이름, u32 타입(0 수, 1 text), 8 B 기본값: 수는 f64, text는 u32 상수 |
-| | 보기 × 16 B: u32 이름, u8 타입(0 수, 1 text, 2 표), u8 0, u16 표(열이면 그 표 보기 + 1, 아니면 0), u32 시작 명령, u32 명령 수. 표는 시작 명령 자리에 리스트 상태, 명령 수 0 |
+| | 보기 × 16 B: u32 이름, u8 타입(0 수, 1 text, 2 표), u8 0, u16 표(열이면 그 표 보기 + 1, 아니면 0), u32 시작 명령, u32 명령 수. 표는 시작 명령 자리에 리스트 상태, 명령 수 0. 걸러진 표는 타입 다음 바이트에 리스트 상태 + 1, 시작·명령 수에 필터 블록 |
| | 핸들러 × 12 B: u32 이벤트, u32 시작 명령, u32 명령 수 |
| | 명령 × 16 B: u8 opcode, 0 3바이트, u32 인자, f64 값 |
| | 문자열: `[u8 길이][바이트]`의 연속 |
@@ -122,6 +122,8 @@ v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마�
- 집계(FOLD)의 블록은 그 리스트의 열로 따로 검증한다. 읽기만 하고(효과는 거절), count는 수 하나, sum·max·min은 수 둘을
남긴다. 블록 안에 다시 집계를 둘 수 없다(비용 상한). 둘레의 블록은 FOLD와 그 블록을 명령 하나로 보고, 앞의 분기가
블록 안으로 들어오지 못한다. 블록은 둘레 블록 안에 끝나야 한다.
+- 걸러진 표의 필터 블록은 그 리스트의 열로 검증한다. 읽기만 하고 수 하나를 남기며, 안에 집계를 둘 수 없다.
+ 타입 다음 바이트는 표에만 쓸 수 있다.
- 표는 리스트 상태를 가리키고 열은 앞에 나온 표를 가리킨다. 표 밖의 이름은 한 이름공간이고, 열 이름은 표
안에서만 유일하다.
@@ -161,7 +163,11 @@ text·리스트가 남을 때만 위치를 유지한다.
`pure_module_position`은 수 보기가 인덱스 상태의 위치 그대로(`view tasks_focus = at.position`)이면 그 상태를 돌려준다.
그래서 인터페이스(Vega)는 앱 코드 없이 목록의 클릭을 포커스로 옮긴다.
-표 보기는 리스트 항목마다 행 하나를 가진다(`pure_app_rows`). 호스트는 보여 줄 칸만
+표 보기는 리스트 항목마다 행 하나를 가진다(`pure_app_rows`). 걸러진 표는 필터 블록이 0이 아닌 수를 남긴 항목마다
+행 하나를 가진다(항목 순서 그대로). 필터는 상태가 바뀐 뒤 처음 행을 물을 때 항목마다 한 번 돌고, 그 결과(행 → 항목)를
+다음 변경까지 쓴다. 칸마다 다시 거르지 않는다. 변경 번호는 이벤트, 가리키기, 저장 실패의 되돌리기, 모듈 교체마다
+오르고 되돌아가지 않는다. 열의 `ROW`는 행이 아니라 항목이다. 호스트는 `pure_app_row_item`·`pure_app_item_row`로 행과
+항목을 오간다. 그래서 포커스(인덱스, 곧 항목)를 걸러진 목록의 행으로 보이고, 클릭한 행을 항목으로 가리킨다. 호스트는 보여 줄 칸만
`pure_app_cell`·`pure_app_cell_text`로 행 번호와 함께 계산한다. 화면 모델을 따로 만들거나 목록을 복사하지 않으므로,
10만 항목 목록에서 20줄을 그리는 표면은 20행만 계산한다(가상 스크롤이 런타임의 성질이 된다). 보기가 실패해도
앱의 마지막 이벤트 실패 정보는 바뀌지 않는다.
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;
}