diff options
Diffstat (limited to 'prototypes/connections/module.c')
| -rw-r--r-- | prototypes/connections/module.c | 345 |
1 files changed, 313 insertions, 32 deletions
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c index 950bb3e..c07b1e8 100644 --- a/prototypes/connections/module.c +++ b/prototypes/connections/module.c @@ -6,13 +6,15 @@ #include <string.h> enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES = 8, - CONSTANT_BYTES = 8, VIEW_BYTES = 16, HANDLER_BYTES = 12, OP_BYTES = 16 }; -enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5 /* type of the named field */ }; + CONSTANT_BYTES = 8, RECORD_BYTES = 8, RECORD_FIELD_BYTES = 16, VIEW_BYTES = 16, + HANDLER_BYTES = 12, OP_BYTES = 16 }; +/* T_FIELD and T_ITEM stand for the type of the message or record field named. */ +enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7 }; enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA }; /* Where an opcode may appear. */ enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16 }; enum { READ = IN_VIEW | IN_HANDLER }; -static const unsigned char magic[8] = {'P','U','R','E','M','O','D','3'}; +static const unsigned char magic[8] = {'P','U','R','E','M','O','D','4'}; /* Operand types popped (bottom to top), the type pushed, and where allowed. */ static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP_COUNT] = { @@ -66,6 +68,19 @@ static const struct { unsigned char pops, in[3], out, where; } signature[PURE_OP [PURE_OP_TEXT_FROM] = {1, {T_BYTES}, T_TEXT, READ}, [PURE_OP_BYTES_FROM] = {1, {T_TEXT}, T_BYTES, READ}, [PURE_OP_CUR_COUNT] = {1, {T_CURSOR}, T_NUM, READ}, + /* Record fields pop as many values as the list's record has fields. */ + [PURE_OP_LIST_COUNT] = {0, {0}, T_NUM, READ}, + [PURE_OP_LIST_APPEND] = {0, {0}, 0, IN_HANDLER}, + [PURE_OP_LIST_INSERT] = {1, {T_INDEX}, 0, IN_HANDLER}, + [PURE_OP_LIST_REMOVE] = {1, {T_INDEX}, 0, IN_HANDLER}, + [PURE_OP_ITEM_GET] = {1, {T_INDEX}, T_ITEM, READ}, + [PURE_OP_ITEM_SET] = {1, {T_INDEX}, 0, IN_HANDLER}, + [PURE_OP_INDEX_LOAD] = {0, {0}, T_INDEX, READ}, + [PURE_OP_INDEX_STORE] = {1, {T_INDEX}, 0, IN_HANDLER}, + [PURE_OP_INDEX_MOVE] = {2, {T_INDEX, T_NUM}, T_INDEX, READ}, + [PURE_OP_INDEX_FIRST] = {0, {0}, T_INDEX, READ}, + [PURE_OP_INDEX_LAST] = {0, {0}, T_INDEX, READ}, + [PURE_OP_INDEX_NUM] = {1, {T_INDEX}, T_NUM, READ}, }; static uint32_t read16(const unsigned char *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8; } @@ -133,7 +148,10 @@ static bool uses_arg(uint8_t op) { case PURE_OP_DOC: case PURE_OP_DOC_SIZE: case PURE_OP_REPLACE: case PURE_OP_TEXT_CONST: case PURE_OP_TEXT_LOAD: case PURE_OP_CUR_LOAD: case PURE_OP_CUR_START: case PURE_OP_CUR_END: case PURE_OP_CUR_MOVE: case PURE_OP_CUR_STORE: case PURE_OP_TEXT_INSERT: case PURE_OP_TEXT_REMOVE: - case PURE_OP_TEXT_LINE: case PURE_OP_TEXT_SET: case PURE_OP_CUR_COUNT: + case PURE_OP_TEXT_LINE: case PURE_OP_TEXT_SET: case PURE_OP_CUR_COUNT: case PURE_OP_LIST_COUNT: + case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: case PURE_OP_ITEM_GET: + case PURE_OP_ITEM_SET: case PURE_OP_INDEX_LOAD: case PURE_OP_INDEX_STORE: case PURE_OP_INDEX_FIRST: + case PURE_OP_INDEX_LAST: return true; default: return false; } @@ -144,10 +162,19 @@ static bool state_of(const PureModule *m, uint32_t state, uint8_t type) { return state < m->states && m->state_type[state] == type; } +/* The list an item operation names: its low 16 bits (the field is above). */ +static uint32_t list_of(uint32_t arg) { return arg & 0xFFFF; } +static uint32_t item_field(uint32_t arg) { return arg >> 16; } + +static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t field) { + return m->record_field_text[m->record[m->state_ref[list]].first + field] ? T_TEXT : T_NUM; +} + /* Types and heights are checked per instruction; a cursor's type records - * which text it belongs to. Branches go forward within the block and every - * join happens with an empty operand stack, so control flow never merges - * typed values. Handlers end empty, views with one value of their type. */ + * which text it belongs to and an index's type which list. Branches go + * forward within the block and every join happens with an empty operand + * stack, so control flow never merges typed values. Handlers end empty, + * views with one value of their type. */ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_t event, char *error, size_t size, unsigned index) { if (block.start > m->code_count || block.length > m->code_count - block.start) @@ -192,13 +219,32 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_ break; case PURE_OP_CUR_LOAD: case PURE_OP_CUR_STORE: known = state_of(m, arg, PURE_STATE_CURSOR); break; case PURE_OP_CUR_MOVE: case PURE_OP_CUR_COUNT: known = arg <= PURE_UNIT_LINE; break; + case PURE_OP_LIST_COUNT: case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: + case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: + known = state_of(m, arg, PURE_STATE_LIST); + break; + case PURE_OP_ITEM_GET: case PURE_OP_ITEM_SET: + known = state_of(m, list_of(arg), PURE_STATE_LIST) && + item_field(arg) < m->record[m->state_ref[list_of(arg)]].count; + break; + case PURE_OP_INDEX_LOAD: case PURE_OP_INDEX_STORE: known = state_of(m, arg, PURE_STATE_INDEX); break; default: break; } if (!known) return reject(error, size, "operand names no such state, field, constant or unit", pc); + /* Operand types, bottom to top; record operations take their fields. */ + unsigned char need[PURE_RECORD_FIELDS + 2]; unsigned pops = signature[op->op].pops; + memcpy(need, signature[op->op].in, sizeof(signature[op->op].in)); + if (op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT) { + const PureRecord *record = &m->record[m->state_ref[arg]]; + for (unsigned f = 0; f < record->count; ++f) need[pops + f] = field_type(m, arg, f); + pops += record->count; + } else if (op->op == PURE_OP_ITEM_SET) { + need[pops++] = field_type(m, list_of(arg), item_field(arg)); + } if (height < (int)pops) return reject(error, size, "stack underflow", pc); for (unsigned k = 0; k < pops; ++k) - if (types[height - (int)pops + (int)k] != signature[op->op].in[k]) + if (types[height - (int)pops + (int)k] != need[k]) return reject(error, size, "operand type", pc); /* The text a cursor operand belongs to. */ int owner = pops && types[height - 1] == T_CURSOR ? owners[height - 1] : @@ -211,13 +257,26 @@ static bool verify_block(const PureModule *m, PureBlock block, int view, uint32_ default: break; } if (!same_text) return reject(error, size, "cursor of another text", pc); + /* The list an index operand belongs to; the index is the first operand. */ + unsigned index_owner = need[0] == T_INDEX ? owners[height - (int)pops] : 0; + bool same_list = true; + switch (op->op) { + case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: same_list = index_owner == arg; break; + case PURE_OP_ITEM_GET: case PURE_OP_ITEM_SET: same_list = index_owner == list_of(arg); break; + case PURE_OP_INDEX_STORE: same_list = index_owner == m->state_ref[arg]; break; + default: break; + } + if (!same_list) return reject(error, size, "index of another list", pc); height -= (int)pops; unsigned char out = signature[op->op].out; if (out == T_FIELD) out = m->field[shape->field_start + arg].type == PURE_FIELD_U32 ? T_NUM : T_BYTES; + if (out == T_ITEM) out = field_type(m, list_of(arg), item_field(arg)); if (out) { if (height == PURE_MODULE_STACK) return reject(error, size, "stack overflow", pc); - owners[height] = (unsigned char)(op->op == PURE_OP_CUR_LOAD ? m->state_ref[arg] : - op->op == PURE_OP_CUR_START || op->op == PURE_OP_CUR_END ? arg : + owners[height] = (unsigned char)(op->op == PURE_OP_CUR_LOAD || op->op == PURE_OP_INDEX_LOAD ? m->state_ref[arg] : + op->op == PURE_OP_CUR_START || op->op == PURE_OP_CUR_END || + op->op == PURE_OP_INDEX_FIRST || op->op == PURE_OP_INDEX_LAST ? arg : + op->op == PURE_OP_INDEX_MOVE ? index_owner : owner >= 0 ? (unsigned)owner : 0); types[height++] = out; } @@ -242,7 +301,7 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out, if (error_size) error[0] = 0; memset(out, 0, sizeof(*out)); if (!p || size < PURE_MODULE_HEADER || memcmp(p, magic, sizeof(magic))) - return reject(error, error_size, "not a Pure module v3", 0), PURE_INVALID; + return reject(error, error_size, "not a Pure module v4", 0), PURE_INVALID; out->states = (uint16_t)read16(p + 8); out->inputs = (uint16_t)read16(p + 10); out->views = (uint16_t)read16(p + 12); @@ -252,17 +311,21 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out, out->fields = (uint16_t)read16(p + 24); out->collections = (uint16_t)read16(p + 26); out->constants = (uint16_t)read16(p + 28); + out->records = (uint16_t)read16(p + 30); uint32_t constant_bytes = read32(p + 32); - if (read16(p + 30) || read32(p + 36)) return reject(error, error_size, "reserved header bytes", 30), PURE_INVALID; + out->record_fields = (uint16_t)read16(p + 36); + if (read16(p + 38)) return reject(error, error_size, "reserved header bytes", 38), PURE_INVALID; if (out->states > PURE_MODULE_STATES || out->inputs > PURE_MODULE_INPUTS || out->views > PURE_MODULE_VIEWS || out->handlers > PURE_MODULE_HANDLERS || out->code_count > PURE_MODULE_CODE || strings > PURE_MODULE_STRINGS || out->fields > PURE_MODULE_FIELDS || out->collections > PURE_MODULE_COLLECTIONS || - out->constants > PURE_MODULE_CONSTANTS || constant_bytes > PURE_MODULE_CONSTANT_BYTES) + out->constants > PURE_MODULE_CONSTANTS || constant_bytes > PURE_MODULE_CONSTANT_BYTES || + out->records > PURE_MODULE_RECORDS || out->record_fields > PURE_MODULE_RECORD_FIELDS) return reject(error, error_size, "module exceeds a limit", 8), PURE_LIMIT; size_t expected = PURE_MODULE_HEADER + (size_t)out->states * STATE_BYTES + (size_t)out->collections * COLLECTION_BYTES + (size_t)out->inputs * INPUT_BYTES + (size_t)out->fields * FIELD_BYTES + (size_t)out->constants * CONSTANT_BYTES + + (size_t)out->records * RECORD_BYTES + (size_t)out->record_fields * RECORD_FIELD_BYTES + (size_t)out->views * VIEW_BYTES + (size_t)out->handlers * HANDLER_BYTES + (size_t)out->code_count * OP_BYTES + strings + constant_bytes; if (size != expected) return reject(error, error_size, "module size", (unsigned)expected), PURE_INVALID; @@ -273,7 +336,7 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out, out->state_type[i] = (uint8_t)read32(at + 4); if (!name_at(text, strings, read32(at), true, out->state_names[i])) return reject(error, error_size, "state name", i), PURE_INVALID; - if (read32(at + 4) > PURE_STATE_CURSOR) return reject(error, error_size, "state type", i), PURE_INVALID; + if (read32(at + 4) > PURE_STATE_INDEX) return reject(error, error_size, "state type", i), PURE_INVALID; if (out->state_type[i] == PURE_STATE_NUM) { out->initial[i] = read_double(at + 8); if (!isfinite(out->initial[i])) return reject(error, error_size, "non-finite initial value", i), PURE_INVALID; @@ -343,9 +406,45 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out, !pure_utf8_valid((PureSpan){out->constant_data + out->constant_offset[i], out->constant_length[i]})) return reject(error, error_size, "text constant", i), PURE_INVALID; } + /* Records: fields in order, numbered from first; a text default is a constant. */ + const unsigned char *fields_at = at + (size_t)out->records * RECORD_BYTES; + uint32_t next_record_field = 0; + for (uint32_t i = 0; i < out->records; ++i, at += RECORD_BYTES) { + PureRecord *record = &out->record[i]; + record->first = (uint16_t)read16(at + 4); + record->count = (uint16_t)read16(at + 6); + if (!name_at(text, strings, read32(at), true, out->record_names[i]) || record->first != next_record_field || + !record->count || record->count > PURE_RECORD_FIELDS || record->count > out->record_fields - next_record_field) + return reject(error, error_size, "record", i), PURE_INVALID; + for (uint32_t f = record->first; f < record->first + record->count; ++f) { + const unsigned char *entry = fields_at + (size_t)f * RECORD_FIELD_BYTES; + uint32_t type = read32(entry + 4); + out->record_field_text[f] = type == 1; + if (!name_at(text, strings, read32(entry), true, out->record_field_names[f]) || type > 1) + return reject(error, error_size, "record field", f), PURE_INVALID; + for (uint32_t k = record->first; k < f; ++k) + if (!strcmp(out->record_field_names[k], out->record_field_names[f])) + return reject(error, error_size, "duplicate record field", f), PURE_INVALID; + if (type == 1) { + if (read32(entry + 12) || read32(entry + 8) >= out->constants) + return reject(error, error_size, "record field default", f), PURE_INVALID; + out->record_field_default[f] = read32(entry + 8); + } else { + out->record_field_default[f] = read_double(entry + 8); + if (!isfinite(out->record_field_default[f])) + return reject(error, error_size, "record field default", f), PURE_INVALID; + } + } + next_record_field += record->count; + } + if (next_record_field != out->record_fields) + return reject(error, error_size, "unused record fields", next_record_field), PURE_INVALID; + at = fields_at + (size_t)out->record_fields * RECORD_FIELD_BYTES; for (uint32_t i = 0; i < out->states; ++i) if ((out->state_type[i] == PURE_STATE_TEXT && out->state_ref[i] >= out->constants) || - (out->state_type[i] == PURE_STATE_CURSOR && !state_of(out, out->state_ref[i], PURE_STATE_TEXT))) + (out->state_type[i] == PURE_STATE_CURSOR && !state_of(out, out->state_ref[i], PURE_STATE_TEXT)) || + (out->state_type[i] == PURE_STATE_LIST && out->state_ref[i] >= out->records) || + (out->state_type[i] == PURE_STATE_INDEX && !state_of(out, out->state_ref[i], PURE_STATE_LIST))) return reject(error, error_size, "state reference", i), PURE_INVALID; for (uint32_t i = 0; i < out->views; ++i, at += VIEW_BYTES) { if (!name_at(text, strings, read32(at), true, out->view_names[i])) @@ -365,10 +464,12 @@ PureResult pure_module_load(const void *bytes, size_t size, PureModule *out, if (at[1] || at[2] || at[3]) return reject(error, error_size, "instruction padding", i), PURE_INVALID; out->code[i] = (PureOp){.op = at[0], .arg = read32(at + 4), .value = read_double(at + 8)}; } - /* States, collections, inputs and views share one namespace. */ - const char *names[PURE_MODULE_STATES + PURE_MODULE_COLLECTIONS + PURE_MODULE_INPUTS + PURE_MODULE_VIEWS]; + /* States, records, collections, inputs and views share one namespace. */ + const char *names[PURE_MODULE_STATES + PURE_MODULE_RECORDS + PURE_MODULE_COLLECTIONS + + PURE_MODULE_INPUTS + PURE_MODULE_VIEWS]; size_t count = 0; for (uint32_t i = 0; i < out->states; ++i) names[count++] = out->state_names[i]; + for (uint32_t i = 0; i < out->records; ++i) names[count++] = out->record_names[i]; for (uint32_t i = 0; i < out->collections; ++i) names[count++] = out->collection_names[i]; for (uint32_t i = 0; i < out->inputs; ++i) names[count++] = out->input_names[i]; for (uint32_t i = 0; i < out->views; ++i) names[count++] = out->view_names[i]; @@ -508,6 +609,26 @@ static void move_cursors(const PureModule *m, double *values, uint32_t text, siz } } +/* Indices follow their item across an insert or remove; an index whose item + * was removed names the next item (or the last, or 0 in an empty list). */ +static void move_indices(const PureModule *m, double *values, uint32_t list, size_t count, + size_t at, bool inserted) { + double last = count ? (double)(count - 1) : 0; + for (uint32_t i = 0; i < m->states; ++i) { + if (m->state_type[i] != PURE_STATE_INDEX || m->state_ref[i] != list) continue; + if (inserted) { + if (count > 1 && values[i] >= (double)at) ++values[i]; + } else { + if (values[i] > (double)at) --values[i]; + values[i] = minimum(values[i], last); + } + } +} + +static bool text_field(const PureModule *m, uint32_t list, uint32_t field) { + return m->record_field_text[m->record[m->state_ref[list]].first + field]; +} + /* Run a verified block. Types and stack bounds were proven at load. */ static PureResult run(const PureModule *m, double *values, PureBlock block, Event *ev, Slot *result) { Slot stack[PURE_MODULE_STACK]; @@ -697,6 +818,105 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even continue; } case PURE_OP_BYTES_FROM: ++pc; continue; /* same bytes, now untyped */ + case PURE_OP_LIST_COUNT: stack[sp++].num = (double)pure_list_count(app->lists[op->arg]); ++pc; continue; + case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: { + PureList *list = app->lists[op->arg]; + const PureRecord *record = &m->record[m->state_ref[op->arg]]; + size_t base = sp - record->count, count = pure_list_count(list), at = count; + sp = base; + if (op->op == PURE_OP_LIST_INSERT && (at = (size_t)stack[--sp].num) > count) + return fault(app, PURE_FAULT_NOT_FOUND, pc); + /* Room for every text first, so no span moves while the next is copied. */ + size_t mark = ev->arena->used, total = 0; + for (unsigned f = 0; f < record->count; ++f) + if (text_field(m, op->arg, f)) total += (size_t)stack[base + f].bytes.length; + if (!reserve(ev->arena, total)) return PURE_ALLOCATION; + PureCell cells[PURE_RECORD_FIELDS]; + for (unsigned f = 0; f < record->count; ++f) { + PureSpan span = {0}; + if (text_field(m, op->arg, f) && + (status = contiguous(app, ev->arena, stack[base + f].bytes, &span)) != PURE_OK) return status; + cells[f] = (PureCell){stack[base + f].num, (unsigned char *)span.data, span.size}; + } + status = pure_list_insert(list, at, cells, &app->undo); + ev->arena->used = mark; + if (status == PURE_LIMIT) return fault(app, PURE_FAULT_LIMIT, pc); + if (status != PURE_OK) return status; + move_indices(m, values, op->arg, pure_list_count(list), at, true); + ++pc; + continue; + } + case PURE_OP_LIST_REMOVE: { + size_t at = (size_t)stack[--sp].num; + status = pure_list_remove(app->lists[op->arg], at, &app->undo); + if (status == PURE_NOT_FOUND) return fault(app, PURE_FAULT_NOT_FOUND, pc); + if (status != PURE_OK) return status; + move_indices(m, values, op->arg, pure_list_count(app->lists[op->arg]), at, false); + ++pc; + continue; + } + case PURE_OP_ITEM_GET: { + uint32_t list = list_of(op->arg), field = item_field(op->arg); + size_t at = (size_t)stack[sp - 1].num; + if (at >= pure_list_count(app->lists[list])) return fault(app, PURE_FAULT_NOT_FOUND, pc); + PureCell cell = pure_list_get(app->lists[list], at, field); + if (!text_field(m, list, field)) stack[sp - 1].num = cell.num; + else { + /* A copy: the item may change or go before the value is used. */ + if (!reserve(ev->arena, cell.size)) return PURE_ALLOCATION; + if (cell.size) memcpy(ev->arena->bytes + ev->arena->used, cell.text, cell.size); + stack[sp - 1].bytes = (PureBytes){FROM_ARENA, 0, {0, 0}, ev->arena->used, cell.size}; + ev->arena->used += cell.size; + } + ++pc; + continue; + } + case PURE_OP_ITEM_SET: { + uint32_t list = list_of(op->arg), field = item_field(op->arg); + Slot value = stack[--sp]; + size_t at = (size_t)stack[--sp].num, mark = ev->arena->used; + PureSpan span = {0}; + if (text_field(m, list, field) && (status = contiguous(app, ev->arena, value.bytes, &span)) != PURE_OK) + return status; + status = pure_list_set(app->lists[list], at, field, + (PureCell){value.num, (unsigned char *)span.data, span.size}, &app->undo); + ev->arena->used = mark; + if (status == PURE_NOT_FOUND) return fault(app, PURE_FAULT_NOT_FOUND, pc); + if (status != PURE_OK) return status; + ++pc; + continue; + } + case PURE_OP_INDEX_LOAD: + stack[sp].num = values[op->arg]; + stack[sp++].bytes.owner = m->state_ref[op->arg]; + ++pc; + continue; + case PURE_OP_INDEX_STORE: { + /* An index from before a remove may be past the end. */ + double position = stack[--sp].num; + if (position && position >= (double)pure_list_count(app->lists[m->state_ref[op->arg]])) + return fault(app, PURE_FAULT_NOT_FOUND, pc); + values[op->arg] = position; + ++pc; + continue; + } + case PURE_OP_INDEX_MOVE: { + double count = stack[--sp].num; + if (count != floor(count)) return fault(app, PURE_FAULT_LIMIT, pc); + size_t items = pure_list_count(app->lists[stack[sp - 1].bytes.owner]); + double last = items ? (double)(items - 1) : 0; + stack[sp - 1].num = maximum(0, minimum(stack[sp - 1].num + count, last)); + ++pc; + continue; + } + case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: { + size_t items = pure_list_count(app->lists[op->arg]); + stack[sp].num = op->op == PURE_OP_INDEX_LAST && items ? (double)(items - 1) : 0; + stack[sp++].bytes.owner = (uint16_t)op->arg; + ++pc; + continue; + } + case PURE_OP_INDEX_NUM: ++pc; continue; /* the position itself */ default: break; } b = stack[--sp].num; @@ -737,13 +957,18 @@ static PureResult new_text(PureDraft **out, const PureModule *m, uint32_t consta return result; } +static PureList *new_list(const PureModule *m, uint32_t record) { + return pure_list_new(m->record[record].count, m->record_field_text + m->record[record].first); +} + PureResult pure_app_start(PureApp *app, const PureModule *module, PureData data) { *app = (PureApp){.module = module, .data = data, .fault_pc = UINT32_MAX}; memcpy(app->values, module->initial, sizeof(double) * module->states); for (uint32_t i = 0; i < module->states; ++i) { - if (module->state_type[i] == PURE_STATE_CURSOR) app->values[i] = 0; - if (module->state_type[i] != PURE_STATE_TEXT) continue; - PureResult result = new_text(&app->texts[i], module, module->state_ref[i]); + PureResult result = PURE_OK; + if (module->state_type[i] == PURE_STATE_TEXT) result = new_text(&app->texts[i], module, module->state_ref[i]); + if (module->state_type[i] == PURE_STATE_LIST && !(app->lists[i] = new_list(module, module->state_ref[i]))) + result = PURE_ALLOCATION; if (result != PURE_OK) { pure_app_stop(app); return result; } } return PURE_OK; @@ -761,7 +986,11 @@ void pure_app_finish(PureApp *app) { void pure_app_stop(PureApp *app) { pure_app_finish(app); - for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) pure_draft_close(app->texts[i]); + for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) { + pure_draft_close(app->texts[i]); + pure_list_free(app->lists[i]); + } + pure_undo_free(&app->undo); free(app->arena.bytes); free(app->view_arena.bytes); memset(app, 0, sizeof(*app)); @@ -779,12 +1008,16 @@ static void hold_texts(PureApp *app, Texts *texts) { } } -static void release_texts(PureApp *app, const Texts *texts, bool keep) { +/* Texts return to their versions and lists undo their log, or both keep + * what the event did. */ +static void settle(PureApp *app, const Texts *texts, bool keep) { for (uint32_t i = 0; i < app->module->states; ++i) if (app->texts[i]) { if (!keep) pure_draft_restore(app->texts[i], texts->before[i]); pure_draft_hold(app->texts[i], false); } + if (keep) pure_undo_commit(&app->undo); + else pure_undo_rollback(&app->undo); } /* All handlers of one event run in module order against pending states. */ @@ -806,7 +1039,7 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { Texts texts; hold_texts(app, &texts); PureResult result = run_handlers(app, event, ev, pending); - release_texts(app, &texts, result == PURE_OK); + settle(app, &texts, result == PURE_OK); if (result == PURE_OK) { memcpy(app->values, pending, sizeof(pending)); ++app->events; @@ -885,8 +1118,8 @@ PureResult pure_app_message(PureApp *app, uint32_t input, const char *key, int64 result = keep_reply(app); if (result == PURE_OK && ev.modified) result = app->data.commit(app->data.context, app->document, &app->revision); - /* States and texts change only with a successful commit (or no document edit). */ - release_texts(app, &texts, result == PURE_OK); + /* States, texts and lists change only with a successful commit (or no document edit). */ + settle(app, &texts, result == PURE_OK); if (result == PURE_OK) { memcpy(app->values, pending, sizeof(pending)); ++app->events; @@ -922,11 +1155,45 @@ 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; } +/* A kept list keeps its items. When its record changed, each field takes the + * old field of the same name and kind, or its default. */ +static PureResult keep_list(PureList **out, PureList *from, const PureModule *old, uint32_t old_record, + const PureModule *m, uint32_t record) { + const PureRecord *r = &m->record[record], *o = &old->record[old_record]; + int source[PURE_RECORD_FIELDS]; + bool same = r->count == o->count; + for (unsigned f = 0; f < r->count; ++f) { + source[f] = -1; + for (unsigned k = 0; k < o->count; ++k) + if (!strcmp(m->record_field_names[r->first + f], old->record_field_names[o->first + k]) && + m->record_field_text[r->first + f] == old->record_field_text[o->first + k]) source[f] = (int)k; + same = same && source[f] == (int)f; + } + if (same) { *out = from; return PURE_OK; } + if (!(*out = new_list(m, record))) return PURE_ALLOCATION; + PureResult result = PURE_OK; + for (size_t at = 0; result == PURE_OK && at < pure_list_count(from); ++at) { + PureCell cells[PURE_RECORD_FIELDS]; + for (unsigned f = 0; f < r->count; ++f) { + uint32_t field = r->first + f, constant = (uint32_t)m->record_field_default[field]; + cells[f] = source[f] >= 0 ? pure_list_get(from, at, (unsigned)source[f]) : + m->record_field_text[field] ? + (PureCell){0, (unsigned char *)m->constant_data + m->constant_offset[constant], + m->constant_length[constant]} : + (PureCell){m->record_field_default[field], NULL, 0}; + } + result = pure_list_insert(*out, at, cells, NULL); + } + if (result != PURE_OK) { pure_list_free(*out); *out = NULL; } + return result; +} + PureResult pure_app_replace(PureApp *app, const PureModule *module) { pure_app_finish(app); const PureModule *old = app->module; double values[PURE_MODULE_STATES] = {0}; PureDraft *texts[PURE_MODULE_STATES] = {0}; + PureList *lists[PURE_MODULE_STATES] = {0}; int kept[PURE_MODULE_STATES]; PureResult result = PURE_OK; for (uint32_t i = 0; i < module->states; ++i) { @@ -935,27 +1202,41 @@ PureResult pure_app_replace(PureApp *app, const PureModule *module) { if (module->state_type[i] == PURE_STATE_TEXT) { if (kept[i] >= 0) texts[i] = app->texts[kept[i]]; else if (result == PURE_OK) result = new_text(&texts[i], module, module->state_ref[i]); + } else if (module->state_type[i] == PURE_STATE_LIST) { + if (result != PURE_OK) continue; + if (kept[i] >= 0) + result = keep_list(&lists[i], app->lists[kept[i]], old, old->state_ref[kept[i]], module, + module->state_ref[i]); + else if (!(lists[i] = new_list(module, module->state_ref[i]))) result = PURE_ALLOCATION; } else values[i] = kept[i] >= 0 ? app->values[kept[i]] : module->initial[i]; } - /* A cursor keeps its position only while its text is the same kept text. */ + /* A cursor or index keeps its position only while its text or list is the + * one it belonged to. */ for (uint32_t i = 0; i < module->states; ++i) { - if (module->state_type[i] != PURE_STATE_CURSOR) continue; - uint32_t text = module->state_ref[i]; - bool same = kept[i] >= 0 && kept[text] >= 0 && old->state_ref[kept[i]] == (unsigned)kept[text]; + if (module->state_type[i] != PURE_STATE_CURSOR && module->state_type[i] != PURE_STATE_INDEX) continue; + uint32_t owner = module->state_ref[i]; + bool same = kept[i] >= 0 && kept[owner] >= 0 && old->state_ref[kept[i]] == (unsigned)kept[owner]; values[i] = same ? app->values[kept[i]] : 0; } if (result != PURE_OK) { - for (uint32_t i = 0; i < module->states; ++i) + for (uint32_t i = 0; i < module->states; ++i) { if (texts[i] && kept[i] < 0) pure_draft_close(texts[i]); + if (lists[i] && (kept[i] < 0 || lists[i] != app->lists[kept[i]])) pure_list_free(lists[i]); + } return result; } for (uint32_t i = 0; i < old->states; ++i) { bool reused = false; - for (uint32_t k = 0; k < module->states; ++k) reused = reused || (texts[k] && texts[k] == app->texts[i]); - if (app->texts[i] && !reused) pure_draft_close(app->texts[i]); + for (uint32_t k = 0; k < module->states; ++k) + reused = reused || (texts[k] && texts[k] == app->texts[i]) || (lists[k] && lists[k] == app->lists[i]); + if (!reused) { + pure_draft_close(app->texts[i]); + pure_list_free(app->lists[i]); + } } memcpy(app->values, values, sizeof(values)); memcpy(app->texts, texts, sizeof(texts)); + memcpy(app->lists, lists, sizeof(lists)); app->module = module; return PURE_OK; } |
