aboutsummaryrefslogtreecommitdiffstats
path: root/core/module.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/module.c')
-rw-r--r--core/module.c232
1 files changed, 219 insertions, 13 deletions
diff --git a/core/module.c b/core/module.c
index efa6547..db0703c 100644
--- a/core/module.c
+++ b/core/module.c
@@ -17,7 +17,7 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES =
* the named local holds, T_STATE for the type of the number, bool or i64
* state named. */
enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7, T_KEY = 8, T_LOCAL = 9,
- T_BOOL = 10, T_STATE = 11, T_I64 = 12 };
+ T_BOOL = 10, T_STATE = 11, T_I64 = 12, T_LIST = 13 }; /* T_LIST: a list value, its owner the record of its items */
enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA, FROM_ARGUMENTS, FROM_HELD };
/* Where an opcode may appear. */
/* IN_ARGUMENTS: fields read where nothing is done with them (a view's arguments, a message's fields in a fold). */
@@ -144,6 +144,11 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_BYTE] = {1, {T_I64}, T_BYTES, READ, A_NONE, O_ANY},
[PURE_OP_BYTES_LOAD] = {0, {0}, T_BYTES, IN_LOCALS, A_HELD, O_ANY},
[PURE_OP_BYTES_STORE] = {1, {T_BYTES}, 0, IN_HANDLER | IN_COMPUTE, A_HELD, O_ANY},
+ [PURE_OP_LIST_VALUE] = {0, {0}, T_LIST, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ANY},
+ [PURE_OP_LIST_BIND] = {1, {T_LIST}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ANY},
+ /* APPEND_IN also takes as many values as the record of the list in the field has fields. */
+ [PURE_OP_APPEND_IN] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, A_ITEM, O_LIST},
+ [PURE_OP_NUM_PARSE] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY},
};
#undef NUMS
#undef COMPARE
@@ -340,10 +345,44 @@ static bool state_of(const PureModule *m, uint32_t state, uint8_t type) {
static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t field) {
static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT,
- [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64};
+ [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST};
return types[m->record_field_type[m->record[m->state_ref[list]].first + field]];
}
+/* The record of the lists a LIST field of a record holds (field: the module's numbering), or UINT32_MAX. */
+static uint32_t nested_record(const PureModule *m, uint32_t field) {
+ return m->record_field_type[field] == PURE_RECORD_LIST ? (uint32_t)m->record_field_default[field].i64 : UINT32_MAX;
+}
+
+/* An event's list, and one bound by LIST_BIND (a register: never changed by code). */
+static bool event_list_state(const PureModule *m, uint32_t list) {
+ return list < m->states && m->state_type[list] == PURE_STATE_LIST && m->state_flags[list] & PURE_LIST_ROWS >> 8;
+}
+static bool bound_list(const PureModule *m, uint32_t list) {
+ return event_list_state(m, list) && m->state_flags[list] & PURE_LIST_BOUND >> 8;
+}
+
+/* The stack type of a record field (the module's numbering). */
+static unsigned char record_type(const PureModule *m, uint32_t field) {
+ static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT,
+ [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST};
+ return types[m->record_field_type[field]];
+}
+
+/* The record of the list value an instruction takes as its operand k (bottom 0). */
+static unsigned list_wanted(const PureModule *m, const PureOp *op, unsigned k) {
+ uint32_t arg = op->arg;
+ switch (op->op) {
+ case PURE_OP_LIST_BIND: return m->state_ref[arg];
+ case PURE_OP_LIST_APPEND: return nested_record(m, m->record[m->state_ref[arg]].first + k);
+ case PURE_OP_LIST_INSERT: return nested_record(m, m->record[m->state_ref[arg]].first + k - 1); /* the index first */
+ case PURE_OP_ITEM_SET: return nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg));
+ case PURE_OP_APPEND_IN:
+ return nested_record(m, m->record[nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg))].first + k - 1);
+ default: return UINT32_MAX;
+ }
+}
+
/* The stack type of a message field's value (a u32 is a number). */
static unsigned char message_type(uint8_t type) {
return type == PURE_FIELD_TEXT ? T_TEXT : type == PURE_FIELD_BYTES ? T_BYTES : type == PURE_FIELD_BOOL ? T_BOOL :
@@ -517,6 +556,16 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
REFUSE("change of a keyed list's key");
if (op->op == PURE_OP_BYTES_OF && (m->record[m->state_ref[arg]].count != 1 || field_type(m, arg, 0) != T_I64))
REFUSE("bytes of a list not of one i64");
+ /* Lists in records (027's 2b): a register is read alone; values are of an event's lists; APPEND_IN appends to a
+ * list in a field of an event's list. */
+ if (((op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT || op->op == PURE_OP_LIST_REMOVE) && bound_list(m, arg)) ||
+ ((op->op == PURE_OP_ITEM_SET || op->op == PURE_OP_APPEND_IN) && bound_list(m, list_of(arg))))
+ REFUSE("change of a bound list");
+ if (op->op == PURE_OP_LIST_VALUE && !event_list_state(m, arg)) REFUSE("value of a list not an event's");
+ if (op->op == PURE_OP_LIST_BIND && !bound_list(m, arg)) REFUSE("list bound to a list not bound");
+ if (op->op == PURE_OP_APPEND_IN &&
+ (!event_list_state(m, list_of(arg)) || field_type(m, list_of(arg), item_field(arg)) != T_LIST))
+ REFUSE("append into a field not an event's list's list");
/* A view's body changes an event's lists alone: its states are a snapshot's. */
if (body && (op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT || op->op == PURE_OP_LIST_REMOVE ||
op->op == PURE_OP_ITEM_SET) &&
@@ -575,6 +624,10 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
pops += record->count;
} else if (op->op == PURE_OP_ITEM_SET) {
need[pops++] = field_type(m, list_of(arg), item_field(arg));
+ } else if (op->op == PURE_OP_APPEND_IN) { /* an item of the list in the field */
+ const PureRecord *record = &m->record[nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg))];
+ for (unsigned f = 0; f < record->count; ++f) need[pops + f] = record_type(m, record->first + f);
+ pops += record->count;
} else if (need[0] == T_KEY) {
need[0] = field_type(m, arg, m->state_key[arg] - 1u);
} else if (need[0] == T_LOCAL) {
@@ -606,6 +659,8 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
height -= (int)pops;
for (unsigned k = 0; k < pops; ++k)
if (types[height + (int)k] != need[k]) REFUSE("operand type");
+ for (unsigned k = 0; k < pops; ++k) /* a list value goes where a list of its record goes */
+ if (need[k] == T_LIST && owners[height + (int)k] != list_wanted(m, op, k)) REFUSE("list of another record");
unsigned owner = of == O_ARG ? arg : of == O_REF ? m->state_ref[arg] : of == O_LIST ? list_of(arg) : 0;
if (kind == A_LOCAL && arg >> 8 & 0xFF) { /* an index local belongs to its list */
owner = arg >> 16;
@@ -634,6 +689,9 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (out) {
if (height == PURE_MODULE_STACK) REFUSE("stack overflow");
owners[height] = (unsigned char)(out != T_CURSOR && out != T_INDEX ? 0 : of == O_OPERAND ? owners[height] : owner);
+ if (out == T_LIST) /* the record of its items */
+ owners[height] = (unsigned char)(op->op == PURE_OP_LIST_VALUE ? m->state_ref[arg] :
+ nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg)));
types[height++] = out;
}
if (kind == A_TARGET) {
@@ -1116,11 +1174,11 @@ PURE_COLD static unsigned arity(uint8_t op) {
switch (op) {
case PURE_OP_CONST: case PURE_OP_LOAD: case PURE_OP_INDEX_LOAD: case PURE_OP_ROW: case PURE_OP_OUTER_ROW:
case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_FIELD: case PURE_OP_LOCAL_LOAD:
- case PURE_OP_BYTES_OF: case PURE_OP_BYTES_LOAD:
+ case PURE_OP_BYTES_OF: case PURE_OP_BYTES_LOAD: case PURE_OP_LIST_VALUE:
return 0;
case PURE_OP_ITEM_GET: case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: case PURE_OP_NEG: case PURE_OP_NOT:
case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM: case PURE_OP_I64_NEG: case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM:
- case PURE_OP_INDEX_AT: case PURE_OP_BYTE:
+ case PURE_OP_INDEX_AT: case PURE_OP_BYTE: case PURE_OP_NUM_PARSE:
return 1;
case PURE_OP_CLAMP: case PURE_OP_BYTES_SLICE: return 3;
default: return 2;
@@ -1132,7 +1190,7 @@ PURE_COLD static int list_read(const PureOp *op) {
switch (op->op) {
case PURE_OP_ITEM_GET: return (int)list_of(op->arg);
case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_LIST_FIND:
- case PURE_OP_LIST_HAS: case PURE_OP_ROW: case PURE_OP_BYTES_OF: case PURE_OP_INDEX_AT:
+ case PURE_OP_LIST_HAS: case PURE_OP_ROW: case PURE_OP_BYTES_OF: case PURE_OP_INDEX_AT: case PURE_OP_LIST_VALUE:
return (int)op->arg;
default: return -1;
}
@@ -1331,8 +1389,10 @@ PURE_COLD static bool bounds_fixed(const PureModule *m, const PureRange *range,
states |= (uint64_t)1 << (op->arg & 63);
break;
case PURE_OP_LOCAL_STORE: case PURE_OP_LOOP: locals |= (uint64_t)1 << (op->arg & 63); break;
- case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: lists |= (uint64_t)1 << (op->arg & 63); break;
- case PURE_OP_ITEM_SET: lists |= (uint64_t)1 << (list_of(op->arg) & 63); break;
+ case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: case PURE_OP_LIST_BIND:
+ lists |= (uint64_t)1 << (op->arg & 63);
+ break;
+ case PURE_OP_ITEM_SET: case PURE_OP_APPEND_IN: lists |= (uint64_t)1 << (list_of(op->arg) & 63); break;
default: break;
}
}
@@ -1504,7 +1564,9 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
uint32_t flags = value ? 0 : read32(at + 12);
bool cursor = out->state_type[i] == PURE_STATE_CURSOR, list = out->state_type[i] == PURE_STATE_LIST;
bool rows = list && flags & PURE_LIST_ROWS; /* an event's list, keyed or not */
- uint32_t key = list ? flags & ~(uint32_t)PURE_LIST_ROWS : 0;
+ bool bound = list && flags & PURE_LIST_BOUND; /* a register: an event's list, not keyed */
+ uint32_t key = list ? flags & ~(uint32_t)(PURE_LIST_ROWS | PURE_LIST_BOUND) : 0;
+ if (bound && (!rows || key)) REFUSE("state type", i);
if (rows && type & PURE_STATE_TRANSIENT) REFUSE("state type", i); /* an event's own, saved or not */
/* A list of a message's rows is named by the field's path (input.field), no other state. */
if (!name_at(text, strings, read32(at), rows ? NAME_PATH : NAME_IDENTIFIER, out->state_names[i]) ||
@@ -1520,7 +1582,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if ((flags && !(cursor && flags == PURE_CURSOR_STAYS) && !(list && key <= PURE_RECORD_FIELDS)) ||
read32(at + 8) > UINT16_MAX) REFUSE("state reference", i);
out->state_ref[i] = (uint16_t)read32(at + 8);
- out->state_flags[i] = cursor ? (uint8_t)flags : rows ? PURE_LIST_ROWS >> 8 : 0;
+ out->state_flags[i] = cursor ? (uint8_t)flags : (uint8_t)((rows ? PURE_LIST_ROWS >> 8 : 0) | (bound ? PURE_LIST_BOUND >> 8 : 0));
out->state_key[i] = (uint8_t)key;
}
}
@@ -1618,18 +1680,21 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
const unsigned char *entry = fields_at + (size_t)f * RECORD_FIELD_BYTES;
uint32_t type = read32(entry + 4); /* PureRecordType */
out->record_field_type[f] = (uint8_t)type;
- if (!name_at(text, strings, read32(entry), NAME_IDENTIFIER, out->record_field_names[f]) || type > PURE_RECORD_I64)
+ if (!name_at(text, strings, read32(entry), NAME_PATH, out->record_field_names[f]) || type > PURE_RECORD_LIST)
REFUSE("record field", f);
for (uint32_t k = record->first; k < f; ++k)
if (!strcmp(out->record_field_names[k], out->record_field_names[f])) REFUSE("duplicate record field", f);
/* A default: a text's constant, an i64's 8 bytes, a number (a bool's +0 or 1). */
if (type == PURE_RECORD_TEXT) out->record_field_default[f].num = read32(entry + 8);
- else if (type == PURE_RECORD_I64) out->record_field_default[f].i64 = (int64_t)(read32(entry + 8) | (uint64_t)read32(entry + 12) << 32);
+ else if (type == PURE_RECORD_I64 || type == PURE_RECORD_LIST) /* a LIST's: the record of its items */
+ out->record_field_default[f].i64 = (int64_t)(read32(entry + 8) | (uint64_t)read32(entry + 12) << 32);
else out->record_field_default[f].num = read_double(entry + 8);
double given = out->record_field_default[f].num;
if (type == PURE_RECORD_TEXT ? read32(entry + 12) || read32(entry + 8) >= out->constants
+ : type == PURE_RECORD_LIST ? (uint64_t)out->record_field_default[f].i64 >= out->records
: type != PURE_RECORD_I64 && (!isfinite(given) || (type == PURE_RECORD_BOOL && !canonical_bool(given))))
REFUSE("record field default", f);
+ if (type == PURE_RECORD_LIST) out->record_nested[i] = true;
}
next_record_field += record->count;
}
@@ -1644,6 +1709,19 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_BOOL))) ||
(out->state_type[i] == PURE_STATE_INDEX && !state_of(out, out->state_ref[i], PURE_STATE_LIST)))
REFUSE("state reference", i);
+ /* A list in a record is an event's alone (027's stage 2b): no stored, transient or message's list holds one, and no
+ * list is keyed by one; a register is no message's rows. */
+ for (uint32_t i = 0; i < out->states; ++i) {
+ if (out->state_type[i] != PURE_STATE_LIST) continue;
+ bool nested = out->record_nested[out->state_ref[i]];
+ if ((nested && !(out->state_flags[i] & PURE_LIST_ROWS >> 8)) ||
+ (out->state_key[i] && out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_LIST))
+ REFUSE("list in a record outside an event's list", i);
+ }
+ for (uint32_t f = 0; f < out->fields; ++f)
+ if (out->field[f].type == PURE_FIELD_ROWS &&
+ (out->record_nested[out->state_ref[out->field[f].rows]] || out->state_flags[out->field[f].rows] & PURE_LIST_BOUND >> 8))
+ REFUSE("rows field", f);
/* A stored position into what is not stored would come back meaning nothing. */
for (uint32_t i = 0; i < out->states; ++i)
if ((out->state_type[i] == PURE_STATE_CURSOR || out->state_type[i] == PURE_STATE_INDEX) &&
@@ -2171,6 +2249,53 @@ static int hold(PureApp *app, PureArena *arena, unsigned slot, PureBytes v, bool
return 0;
}
+/* ---- Lists in records (027's 2b): the event's lists made for LIST fields, numbered from 1 */
+
+PURE_COLD static PureList *new_list(const PureModule *m, uint32_t record);
+
+/* The values that share each list in the LIST fields of cells (an item's, just stored). */
+static void share_cells(PureApp *app, const PureModule *m, uint32_t record, const PureCell *cells) {
+ const PureRecord *r = &m->record[record];
+ for (unsigned f = 0; f < r->count; ++f)
+ if (m->record_field_type[r->first + f] == PURE_RECORD_LIST && cells[f].i64) ++app->nested.shared[cells[f].i64 - 1];
+}
+
+/* A new list of record's items among the event's (empty, or a copy of from's items): its number, or 0 out of memory. */
+static uint64_t nest(PureApp *app, uint32_t record, const PureList *from) {
+ const PureModule *m = app->module;
+ if (app->nested.count == app->nested.capacity) {
+ size_t capacity = app->nested.capacity ? app->nested.capacity * 2 : 16;
+ PureList **lists = realloc(app->nested.list, capacity * sizeof(*lists));
+ if (lists) app->nested.list = lists;
+ uint32_t *shared = lists ? realloc(app->nested.shared, capacity * sizeof(*shared)) : NULL;
+ if (!shared) return 0;
+ app->nested.shared = shared;
+ app->nested.capacity = capacity;
+ }
+ PureList *list = new_list(m, record);
+ if (!list) return 0;
+ const PureRecord *r = &m->record[record];
+ for (size_t at = 0, count = from ? pure_list_count(from) : 0; at < count; ++at) {
+ PureCell cells[PURE_RECORD_FIELDS];
+ for (unsigned f = 0; f < r->count; ++f) cells[f] = pure_list_get(from, at, f);
+ if (pure_list_insert(list, at, cells, NULL) != PURE_OK) { pure_list_free(list); return 0; }
+ if (m->record_nested[record]) share_cells(app, m, record, cells);
+ }
+ app->nested.list[app->nested.count] = list;
+ app->nested.shared[app->nested.count] = 0;
+ return ++app->nested.count;
+}
+
+/* The bound lists given back their own, and the event's lists in records let go (an event or a view begins). */
+static void unbind(PureApp *app) {
+ for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i)
+ if (app->unbound[i]) app->lists[i] = app->unbound[i], app->unbound[i] = NULL;
+}
+static void drop_nested(PureApp *app) {
+ for (size_t i = 0; i < app->nested.count; ++i) pure_list_free(app->nested.list[i]);
+ app->nested.count = 0;
+}
+
/* NUM_TEXT's text of v in a format (the argument past its type, PureFormat): false where an integer's form meets a
* number that is not a whole one within 2^53. */
static bool formatted(Slot v, uint32_t arg, char *out, size_t *length) {
@@ -3365,6 +3490,7 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
if (status == PURE_INVALID) status = pure_list_insert(list, at, cells, undo_for(app, arg));
arena->used = mark;
if (status != PURE_OK) REFUSED;
+ if (m->record_nested[m->state_ref[arg]]) share_cells(app, m, m->state_ref[arg], cells);
if (m->state_indexed[arg]) move_indices(m, values, arg, pure_list_count(list), at, true);
if (!grows(app, before, list_held(app, list))) { kind = PURE_FAULT_MEMORY; FAILED; }
NEXT;
@@ -3401,11 +3527,18 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
PureSpan span = {0};
status = op->plan & PLAN_TEXT ? contiguous(app, arena, value.bytes, &span) : PURE_OK;
PureCell cell = {.i64 = value.i64, .text = (unsigned char *)span.data, .size = span.size};
+ bool nested = m->record_field_type[m->record[m->state_ref[list]].first + field] == PURE_RECORD_LIST;
+ uint64_t was = nested && status == PURE_OK && at < pure_list_count(app->lists[list]) ?
+ (uint64_t)pure_list_get(app->lists[list], at, field).i64 : 0;
if (status == PURE_OK) /* where its FIND found it (PLAN_ROW: a HASHED list's slot), else its position */
status = op->plan & PLAN_ROW ? pure_list_set_row(app->lists[list], at, field, cell, undo_for(app, list))
: pure_list_set(app->lists[list], at, field, cell, undo_for(app, list));
arena->used = mark;
if (status != PURE_OK) REFUSED;
+ if (nested) { /* the list it holds now is shared once more, the one it held once less */
+ if (cell.i64) ++app->nested.shared[cell.i64 - 1];
+ if (was) --app->nested.shared[was - 1];
+ }
if (!grows(app, before, list_held(app, app->lists[list]))) { kind = PURE_FAULT_MEMORY; FAILED; }
NEXT;
}
@@ -3628,6 +3761,68 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
if (held) STOPPED(PURE_ALLOCATION);
NEXT;
}
+ case PURE_OP_NUM_PARSE: { /* a text written as a JSON number, its nearest number */
+ PureSpan span;
+ size_t mark = arena->used;
+ if ((status = contiguous(app, arena, top->bytes, &span)) != PURE_OK) STOPPED(status);
+ double number;
+ bool read = pure_parse_number(span.data, span.size, &number);
+ arena->used = mark;
+ if (!read) { kind = PURE_FAULT_LIMIT; FAILED; }
+ top->num = number;
+ NEXT;
+ }
+ case PURE_OP_LIST_VALUE: { /* a copy of an event's list's items (an empty one is 0, made of nothing) */
+ if (!pure_list_count(app->lists[arg])) { stack[sp++].i64 = 0; NEXT; }
+ uint64_t made = nest(app, m->state_ref[arg], app->lists[arg]);
+ if (!made) STOPPED(PURE_ALLOCATION);
+ if (!grows(app, (Held){0, 0}, (Held){0, pure_list_size(app->nested.list[made - 1])})) { kind = PURE_FAULT_MEMORY; FAILED; }
+ stack[sp++].i64 = (int64_t)made;
+ NEXT;
+ }
+ case PURE_OP_LIST_BIND: { /* the register reads the list (shared: no one changes it in place from now) */
+ uint64_t list = (uint64_t)stack[--sp].i64;
+ if (!app->unbound[arg]) app->unbound[arg] = app->lists[arg];
+ app->lists[arg] = list ? app->nested.list[list - 1] : app->unbound[arg];
+ if (list) ++app->nested.shared[list - 1];
+ NEXT;
+ }
+ case PURE_OP_APPEND_IN: { /* an item into the list in a field, made the field's own first when another shares it */
+ uint32_t list = list_of(arg), field = item_field(arg), inner = nested_record(m, m->record[m->state_ref[list]].first + field);
+ const PureRecord *record = &m->record[inner];
+ size_t base = sp - record->count, at = (size_t)stack[base - 1].num;
+ sp = base - 1;
+ if (at >= pure_list_count(app->lists[list])) { kind = PURE_FAULT_NOT_FOUND; FAILED; }
+ uint64_t into = (uint64_t)pure_list_get(app->lists[list], at, field).i64;
+ if (!into || app->nested.shared[into - 1] > 1) {
+ uint64_t own = nest(app, inner, into ? app->nested.list[into - 1] : NULL);
+ if (!own) STOPPED(PURE_ALLOCATION);
+ if (into) --app->nested.shared[into - 1];
+ app->nested.shared[own - 1] = 1;
+ if ((status = pure_list_set(app->lists[list], at, field, (PureCell){.i64 = (int64_t)own}, NULL)) != PURE_OK) REFUSED;
+ into = own;
+ }
+ PureList *in = app->nested.list[into - 1];
+ size_t mark = arena->used, total = 0;
+ for (unsigned f = 0; f < record->count; ++f)
+ if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT) total += (size_t)stack[base + f].bytes.length;
+ if (!reserve(arena, total)) STOPPED(PURE_ALLOCATION);
+ PureCell cells[PURE_RECORD_FIELDS];
+ for (unsigned f = 0; f < record->count; ++f) {
+ PureSpan span = {0};
+ if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT &&
+ (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK)
+ STOPPED(status);
+ cells[f] = (PureCell){.i64 = stack[base + f].i64, .text = (unsigned char *)span.data, .size = span.size};
+ }
+ Held before = {0, pure_list_size(in)};
+ status = pure_list_insert(in, pure_list_count(in), cells, NULL);
+ arena->used = mark;
+ if (status != PURE_OK) REFUSED;
+ if (m->record_nested[inner]) share_cells(app, m, inner, cells);
+ if (!grows(app, before, (Held){0, pure_list_size(in)})) { kind = PURE_FAULT_MEMORY; FAILED; }
+ NEXT;
+ }
default: break;
}
double b = stack[--sp].num, a = stack[sp - 1].num, value;
@@ -4031,6 +4226,7 @@ void pure_snapshot_release(PureSnapshot *snapshot) {
static void let_own_go(PureApp *app);
void pure_app_stop(PureApp *app) {
+ unbind(app); /* a register's own list goes with the lists, the bound one with the event's */
pure_app_finish(app);
close_burst(app, true); /* unsaved changes are dropped with the app */
let_own_go(app);
@@ -4050,6 +4246,9 @@ void pure_app_stop(PureApp *app) {
}
pure_retired_free(&app->retired);
for (unsigned i = 0; i < PURE_MODULE_HELD; ++i) free(app->held[i].data);
+ drop_nested(app);
+ free(app->nested.list);
+ free(app->nested.shared);
pure_draft_close(app->stored_values);
free(app->carried);
pure_draft_close(app->stored_links);
@@ -4147,6 +4346,7 @@ static PureResult keep_reply(PureApp *app);
/* Every list of a message's rows emptied: an event's rows are its own. */
static void clear_rows(PureApp *app) {
const PureModule *m = app->module;
+ unbind(app);
for (uint32_t i = 0; i < m->states; ++i)
if (m->state_type[i] == PURE_STATE_LIST && m->state_flags[i] & PURE_LIST_ROWS >> 8) pure_list_clear(app->lists[i]);
}
@@ -4186,6 +4386,7 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) {
app->grown = app->spent = 0;
app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT;
for (unsigned i = 0; m->held && i < PURE_MODULE_HELD; ++i) app->held[i].size = 0;
+ if (app->nested.count) unbind(app), drop_nested(app); /* a view's body bound some on the app itself */
Mark mark;
mark_event(app, &mark);
memcpy(pending, app->values, sizeof(app->values));
@@ -4534,6 +4735,8 @@ static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) {
app->grown = app->spent = 0;
app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT;
for (unsigned i = 0; m->held && i < PURE_MODULE_HELD; ++i) app->held[i].size = 0;
+ unbind(app);
+ if (app->nested.count) drop_nested(app);
}
PureFault fault = app->fault;
uint32_t fault_pc = app->fault_pc;
@@ -4810,7 +5013,7 @@ PureSnapshot *pure_save_snapshot(PureApp *app, uint32_t runtime) {
static void let_own_go(PureApp *app) {
for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) {
pure_list_free(app->own[i]);
- app->own[i] = NULL;
+ app->own[i] = app->unbound[i] = NULL;
}
}
@@ -4826,7 +5029,8 @@ void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) {
uint8_t pure_record_kind(const PureModule *m, uint32_t field) {
static const uint8_t kinds[] = {[PURE_RECORD_NUMBER] = PURE_CELL_NUMBER, [PURE_RECORD_TEXT] = PURE_CELL_TEXT,
- [PURE_RECORD_BOOL] = PURE_CELL_BOOL, [PURE_RECORD_I64] = PURE_CELL_INTEGER};
+ [PURE_RECORD_BOOL] = PURE_CELL_BOOL, [PURE_RECORD_I64] = PURE_CELL_INTEGER,
+ [PURE_RECORD_LIST] = PURE_CELL_INTEGER}; /* a list in a record: its number */
return kinds[m->record_field_type[field]];
}
@@ -5223,6 +5427,8 @@ PURE_COLD PureResult pure_app_start(PureApp *app, const PureModule *module, Pure
}
PURE_COLD PureResult pure_app_replace(PureApp *app, const PureModule *module) {
+ unbind(app); /* lists are laid out again: none bound, the event's in records let go */
+ drop_nested(app);
pure_app_finish(app);
if (app->open || app->pending_count) {
PureResult saved = pure_app_save(app); /* the burst belongs to the module that made it */