diff options
| -rw-r--r-- | core/module-jit.c | 3 | ||||
| -rw-r--r-- | core/module-text.c | 24 | ||||
| -rw-r--r-- | core/module-text.h | 4 | ||||
| -rw-r--r-- | core/module.c | 232 | ||||
| -rw-r--r-- | core/module.h | 29 | ||||
| -rw-r--r-- | core/test-module.c | 76 | ||||
| -rw-r--r-- | docs/design/008-module-format.md | 21 |
7 files changed, 368 insertions, 21 deletions
diff --git a/core/module-jit.c b/core/module-jit.c index 80a242e..0254430 100644 --- a/core/module-jit.c +++ b/core/module-jit.c @@ -1245,7 +1245,8 @@ static bool event_list(const PureModule *m, uint32_t list) { static bool numbers_only(const PureModule *m, uint32_t list) { const PureRecord *record = &m->record[m->state_ref[list]]; for (unsigned f = 0; f < record->count; ++f) - if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT) return false; + if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT || m->record_field_type[record->first + f] == PURE_RECORD_LIST) + return false; /* a list in a record is shared by count (module.c's APPEND_IN) */ return true; } diff --git a/core/module-text.c b/core/module-text.c index 4ce1769..f09cb75 100644 --- a/core/module-text.c +++ b/core/module-text.c @@ -59,6 +59,30 @@ size_t pure_utf8_newlines(PureSpan span) { return count; } +bool pure_parse_number(const unsigned char *text, size_t size, double *value) { + size_t i = 0; + if (i < size && text[i] == '-') ++i; + if (i == size || text[i] < '0' || text[i] > '9') return false; + if (text[i++] != '0') while (i < size && text[i] >= '0' && text[i] <= '9') ++i; + if (i < size && text[i] == '.') { + if (++i == size || text[i] < '0' || text[i] > '9') return false; + while (i < size && text[i] >= '0' && text[i] <= '9') ++i; + } + if (i < size && (text[i] == 'e' || text[i] == 'E')) { + if (++i < size && (text[i] == '+' || text[i] == '-')) ++i; + if (i == size || text[i] < '0' || text[i] > '9') return false; + while (i < size && text[i] >= '0' && text[i] <= '9') ++i; + } + if (i != size) return false; + char small[64], *digits = size < sizeof(small) ? small : malloc(size + 1); /* strtod rounds to nearest, ties to even */ + if (!digits) return false; + memcpy(digits, text, size); + digits[size] = 0; + *value = strtod(digits, NULL); + if (digits != small) free(digits); + return isfinite(*value); +} + size_t pure_format_number(double value, char out[32]) { if (value == 0) { memcpy(out, "0", 2); return 1; } if (value == floor(value) && fabs(value) < 1e15) { diff --git a/core/module-text.h b/core/module-text.h index 4843928..e718a40 100644 --- a/core/module-text.h +++ b/core/module-text.h @@ -24,6 +24,10 @@ size_t pure_utf8_newlines(PureSpan span); * exponent. -0 prints as 0. Returns the length written (at most 32). */ size_t pure_format_number(double value, char out[32]); +/* A number written as JSON writes one, -?(0|[1-9][0-9]*)(.[0-9]+)?([eE][+-]?[0-9]+)?, as the nearest double (ties to + * even): false where the text is not one or its value is not finite (027's NUM_PARSE). */ +bool pure_parse_number(const unsigned char *text, size_t size, double *value); + /* Positions in one version of a text draft (size is version.bytes). */ size_t pure_text_step(const PureDraft *, PureVersion, size_t position, double count); size_t pure_text_line_step(const PureDraft *, PureVersion, size_t position, double count); 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 */ diff --git a/core/module.h b/core/module.h index 01bbb53..52cd4d8 100644 --- a/core/module.h +++ b/core/module.h @@ -45,6 +45,7 @@ typedef enum { PURE_OP_LOOP, PURE_OP_BREAK, PURE_OP_I64_BITS, PURE_OP_BYTE_AT, PURE_OP_BYTES_OF, PURE_OP_INDEX_AT, PURE_OP_BYTES_SLICE, PURE_OP_BYTE, PURE_OP_BYTES_LOAD, PURE_OP_BYTES_STORE, + PURE_OP_LIST_VALUE, PURE_OP_LIST_BIND, PURE_OP_APPEND_IN, PURE_OP_NUM_PARSE, PURE_OP_COUNT } PureOpcode; /* A bool is its own type (docs/design/016-bool-and-i64.md), held as a @@ -90,6 +91,22 @@ typedef enum { PURE_BITS_AND, PURE_BITS_OR, PURE_BITS_XOR, PURE_BITS_LEFT, PURE_ * after the point of FIXED. A form of an integer takes an i64 or a whole * number (else LIMIT), in its base with a sign; FIXED takes a number. */ enum { PURE_MODULE_HELD = 32, PURE_HELD_TEXT = 16, PURE_HELD_APPEND = 1 << 8 }; +/* 027's stage 2b: lists in records. A record field of type LIST holds a list + * of the record its default names (an event's alone: an event's lists, and + * the lists in their items, are the only ones that may hold one). The value + * is the list's number among the event's lists made so (0: an empty one), + * an i64 in its cell, so moves and reads (ITEM_GET) are an integer's; such + * lists go as the event or a view's body ends. A list is a value: storing + * one in a second cell shares it, and a list shared or bound is copied + * before it changes (so no other value sees the change). + * LIST_VALUE pushes the value of an event's list state (the argument): a + * copy of its items. LIST_BIND gives the bound list state (a register: an + * event's list flagged PURE_LIST_BOUND, read and walked, never changed by + * code) the list on the stack. APPEND_IN appends an item (its fields on the + * stack after an index) to the list in field (bits 16-31) of the item the + * index names in list (bits 0-15), an event's list. NUM_PARSE reads a text + * written as a JSON number as the nearest number (failing as LIMIT on any + * other text, or a value past the finite ones). */ typedef enum { PURE_FORMAT_PLAIN, PURE_FORMAT_DECIMAL, PURE_FORMAT_HEX, PURE_FORMAT_HEX_UPPER, PURE_FORMAT_BINARY, PURE_FORMAT_OCTAL, PURE_FORMAT_FIXED } PureFormat; enum { PURE_FORMAT_WIDTH = 8, PURE_FORMAT_ZEROS = 1 << 16, PURE_FORMAT_FORM = 17, PURE_FORMAT_DIGITS = 20 }; @@ -187,7 +204,7 @@ static inline uint8_t pure_value_kind(uint8_t state_type) { state_type == PURE_STATE_I64 ? PURE_CELL_INTEGER : 0xFF; } /* A record field: a number, a text (its default a constant), a bool or an i64. */ -typedef enum { PURE_RECORD_NUMBER = 0, PURE_RECORD_TEXT = 1, PURE_RECORD_BOOL = 2, PURE_RECORD_I64 = 3 } PureRecordType; +typedef enum { PURE_RECORD_NUMBER = 0, PURE_RECORD_TEXT = 1, PURE_RECORD_BOOL = 2, PURE_RECORD_I64 = 3, PURE_RECORD_LIST = 4 } PureRecordType; /* A value's 8 bytes: a number (a bool's 0 or 1, a cursor's or an index's * position too), or an integer. What a value is its state's or field's type * says, and the verifier keeps to it: moves copy the bytes, and only what @@ -199,7 +216,7 @@ typedef union { double num; int64_t i64; } PureValue; * let.name): it is emptied when every event ends, and it is never saved * nor logged for undoing. A message's rows field fills the one its path * names (unkeyed) as the message arrives; code fills a let's (docs/design/018). */ -enum { PURE_CURSOR_STAYS = 1, PURE_LIST_ROWS = 256 }; +enum { PURE_CURSOR_STAYS = 1, PURE_LIST_ROWS = 256, PURE_LIST_BOUND = 512 }; /* A transient state (bit 8 of its type word in a module, a bit of * PureModule.transient): the app holds it while it runs, and undoes it with * a failed event, but never saves it: each start begins it at its initial @@ -327,7 +344,9 @@ typedef struct { PureRecord record[PURE_MODULE_RECORDS]; char record_field_names[PURE_MODULE_RECORD_FIELDS][PURE_MODULE_NAME]; uint8_t record_field_type[PURE_MODULE_RECORD_FIELDS]; /* PureRecordType */ - PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), an i64, or a text constant's index */ + PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), an i64, a text constant's index + * or a list's record (LIST, its i64) */ + bool record_nested[PURE_MODULE_RECORDS]; /* a record with a LIST field (set at load) */ } PureModule; /* Whether a state of m is transient (PURE_STATE_TRANSIENT). */ @@ -522,6 +541,10 @@ typedef struct { bool reading; /* a reader: its states are a snapshot's */ /* The values an event or a view's body holds by name (BYTES_STORE): emptied as it begins. */ struct { unsigned char *data; size_t size, capacity; } held[PURE_MODULE_HELD]; + /* The lists in records an event or a view's body made (LIST fields: a value is a number here, 1 on), how many + * values share each, and the bound lists' own (a register's list while another is bound to it). */ + struct { PureList **list; uint32_t *shared; size_t count, capacity; } nested; + PureList *unbound[PURE_MODULE_STATES]; } PureApp; PureResult pure_app_start(PureApp *, const PureModule *, PureData data); diff --git a/core/test-module.c b/core/test-module.c index 02d5015..f45cada 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -180,7 +180,7 @@ static size_t build(unsigned char *out, Spec spec) { p = put32(p, offset[++n]); p = put32(p, field->text); if (field->text == 1) { p = put32(p, (uint32_t)field->initial); p = put32(p, 0); } - else if (field->text == 3) { + else if (field->text == 3 || field->text == 4) { /* an i64's default, a list's record */ int64_t initial = (int64_t)field->initial; p = put32(p, (uint32_t)(uint64_t)initial); p = put32(p, (uint32_t)((uint64_t)initial >> 32)); @@ -345,7 +345,7 @@ static void rejections(void) { static const O nan_const[] = {{C, 0, NAN}, {ST, 0, 0}}; static const O noncanon[] = {{C, 0, 1}, {NEG, 1, 0}, {ST, 0, 0}}; static const O unreachable[] = {{JMP, 2, 0}, {C, 0, 1}, {C, 0, 1}, {ST, 0, 0}}; - static const O bad_op[] = {{99, 0, 0}, {ST, 0, 0}}; + static const O bad_op[] = {{PURE_OP_COUNT, 0, 0}, {ST, 0, 0}}; /* past the last opcode */ static const H h_all[] = {{0, 0, 1}}; static const H h_two[] = {{0, 0, 2}}; static const H h_five[] = {{0, 0, 5}}; @@ -6247,6 +6247,77 @@ static void formats_and_held(void) { printf("{\"formats_and_held\":{\"formats\":%zu,\"slices\":%zu,\"refused\":%zu}}\n", LEN(shown), LEN(slices), LEN(refused)); } +/* ---- Lists in records (docs/design/027, stage 2b) ------------------------ + * A Node's kids are a list of Leaf. A list made a value (LIST_VALUE) is a + * copy; a value stored in a second cell shares the list, and an append into + * either (APPEND_IN) makes that cell's own first; a register (LIST_BIND) + * reads the list as it was bound, appends after it go to a copy. */ +enum { LV = PURE_OP_LIST_VALUE, BIND = PURE_OP_LIST_BIND, AIN = PURE_OP_APPEND_IN }; +#define NEXT_DIGIT(list) {L, 0, 0}, {C, 0, 10}, {MUL, 0, 0}, {LC, list, 0}, {ADD, 0, 0}, {ST, 0, 0} + +static void lists_in_records(void) { + static const S nums[] = {{"n", 0}}; + static const RF leaf_fields[] = {{"v", 3, 0}}; + static const RF node_fields[] = {{"name", 3, 0}, {"kids", 4, 0}}; /* kids: a list of record 0, Leaf */ + static const R records[] = {{"Leaf", leaf_fields, 1}, {"Node", node_fields, 2}}; + static const T others[] = {{"let.nodes", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}, {"let.leaves", 3 | (uint32_t)PURE_LIST_ROWS << 8, 0}, + {"let.view", 3 | (uint32_t)(PURE_LIST_ROWS | PURE_LIST_BOUND) << 8, 0}, {"i", PURE_STATE_I64, 0}}; + static const I inputs[] = {KEY("go", "KeyG")}; + const O code[] = { + {C, 0, 0}, {ST, 0, 0}, /* 0 n = 0 */ + IC(7), {LA, 2, 0}, IC(9), {LA, 2, 0}, /* 2 leaves = [7, 9] */ + IC(1), {LV, 2, 0}, {LA, 1, 0}, /* 6 nodes.append(Node { 1, leaves }) */ + IC(11), {LA, 2, 0}, /* 9 leaves.append(11): kids keep 2 */ + {XF, 1, 0}, IC(5), {AIN, ITEM(1, 1), 0}, /* 11 nodes[0].kids += 5 */ + IC(2), {XF, 1, 0}, {IG, ITEM(1, 1), 0}, {LA, 1, 0}, /* 14 nodes.append(Node { 2, nodes[0].kids }) */ + {C, 0, 1}, {XAT, 1, 0}, IC(6), {AIN, ITEM(1, 1), 0}, /* 18 nodes[1].kids += 6: its own first */ + {XF, 1, 0}, {IG, ITEM(1, 1), 0}, {BIND, 3, 0}, /* 22 view = nodes[0].kids (3) */ + NEXT_DIGIT(3), /* 25 */ + {XF, 1, 0}, IC(8), {AIN, ITEM(1, 1), 0}, /* 31 nodes[0].kids += 8: bound, so a copy */ + NEXT_DIGIT(3), /* 34 the view still 3 */ + {XF, 1, 0}, {IG, ITEM(1, 1), 0}, {BIND, 3, 0}, NEXT_DIGIT(3), /* 40 nodes[0].kids now 4 */ + {C, 0, 1}, {XAT, 1, 0}, {IG, ITEM(1, 1), 0}, {BIND, 3, 0}, NEXT_DIGIT(3), /* 49 nodes[1].kids: 7 9 5 6 */ + {FOLD, FOLDING(PURE_FOLD_SUM | PURE_FOLD_INTEGER, 3, 3), 0}, {ROW, 3, 0}, {IG, ITEM(3, 0), 0}, {C, 1, 1}, {ST, 4, 0}, + NEXT_DIGIT(2), /* 64 leaves: 3 */ + }; + H handler = {1, 0, LEN(code)}; + Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, code, LEN(code), NULL, 0, others, LEN(others), NULL, 0, NULL, 0, + records, 2, NULL, 0}; + if (load(spec, &module_b) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); } + PureApp app; + CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK); + for (int round = 0; round < 3; ++round) { /* each event begins with its lists and theirs empty */ + CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK); + if (app.values[0].num != 33443 || app.values[4].i64 != 27) { + fprintf(stderr, "lists in records: n %g, i %" PRId64 "\n", app.values[0].num, app.values[4].i64); + abort(); + } + CHECK(pure_list_count(app.lists[1]) == 0 && app.lists[3] && pure_list_count(app.lists[3]) == 0); + } + pure_app_stop(&app); + /* Refused: a change of a register, a bind to a list not bound, an append into a field not a list, a list of another + * record; a stored list of records holding lists. */ + const O refused[][4] = { + {IC(1), {LA, 3, 0}, {C, 0, 0}, {ST, 0, 0}}, + {{XF, 1, 0}, {IG, ITEM(1, 1), 0}, {BIND, 2, 0}, {C, 0, 0}}, + {{XF, 1, 0}, IC(1), {AIN, ITEM(1, 0), 0}, {C, 0, 0}}, + {IC(1), {LV, 1, 0}, {LA, 1, 0}, {C, 0, 0}}, + {{LV, 4, 0}, {C, 0, 0}, {C, 0, 0}, {C, 0, 0}}, + }; + for (size_t k = 0; k < LEN(refused); ++k) { + H one = {1, 0, 3}; + Spec bad = {nums, 1, inputs, 1, NULL, 0, &one, 1, refused[k], 3, NULL, 0, others, LEN(others), NULL, 0, NULL, 0, + records, 2, NULL, 0}; + if (load(bad, &module_b) == PURE_OK) { fprintf(stderr, "lists in records: refused %zu loaded\n", k); abort(); } + } + static const T stored[] = {{"nodes", 3, 1}}; + static const O nothing[] = {{C, 0, 0}, {ST, 0, 0}}; + H two = {1, 0, 2}; + Spec kept = {nums, 1, inputs, 1, NULL, 0, &two, 1, nothing, 2, NULL, 0, stored, 1, NULL, 0, NULL, 0, records, 2, NULL, 0}; + CHECK(load(kept, &module_b) != PURE_OK); + printf("{\"lists_in_records\":{\"rounds\":3,\"refused\":%zu}}\n", LEN(refused) + 1); +} + /* A timer's tick sees the time the host set; floor and a floored modulo * take it apart. now is for handlers only. */ enum { NOW = PURE_OP_NOW, FLOOR = PURE_OP_FLOOR, MOD = PURE_OP_MOD }; @@ -6622,6 +6693,7 @@ int main(void) { integers(); bits_and_bytes(); formats_and_held(); + lists_in_records(); memory_budget(); work_budget(); fold_mutations(); diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index 04b75ee..642da9b 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -22,8 +22,21 @@ v6은 **bool**을 수와 다른 스택 종류로 더한다([016](016-bool-and-i6 v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): 새 명령, 새 타입 번호, 0이던 자리의 값(CONST와 NUM_TEXT의 인자 2, 집계의 PURE_FOLD_INTEGER)이라 판은 그대로다. 그 뒤 [027](027-computation-core.md)의 1단계로 **비트 연산, 자리의 바이트, i64 리스트의 바이트, 자리로 찾은 항목**이 더해졌다(새 명령 -넷, 90–93). 2a로 **바이트 자르기, 한 바이트, 이름으로 담은 바이트와 text**(94–97), CONCAT의 바이트, NUM_TEXT의 형식이 더해졌다. 판은 -그대로다. +넷, 90–93). 2a로 **바이트 자르기, 한 바이트, 이름으로 담은 바이트와 text**(94–97), CONCAT의 바이트, NUM_TEXT의 형식이 더해졌다. 2b로 +**레코드 안의 리스트**(레코드 필드의 타입 4, 98–100), 레지스터(리스트 상태의 플래그 512), 경로 이름의 레코드 필드, **수 읽기**(101)가 +더해졌다. 판은 그대로다. + +**레코드 안의 리스트**(027의 2b). +- 레코드 필드의 타입 4(LIST)는 항목들의 레코드를 기본값 자리(i64)에 둔다. +- 칸의 값은 이벤트가 만든 리스트의 번호(0은 빈 리스트)다. 정수처럼 옮겨지고 ITEM_GET으로 읽힌다. 스택의 타입은 리스트이고, 그 + 레코드를 가진다. +- 그런 레코드는 이벤트의 리스트(let의 것)에만 둔다. 저장 상태, transient, 메시지의 행은 받지 않고, 리스트 필드는 키가 되지 않는다. +- 값으로서의 리스트는 나눠 가질 수 있다. 다른 칸에 담으면 나눠 가지고, 나눠 가지거나 레지스터에 묶인 리스트는 바뀌기 전에 + 복사된다. +- 이벤트(그리고 보기의 본문)가 시작할 때 그 리스트들은 모두 사라진다. 담은 바이트는 이벤트의 몫에 센다. +- 레지스터는 상태 플래그 512(이벤트의 리스트와 함께, 키 없이)를 가진 리스트 상태다. LIST_BIND만이 그것을 바꾸고, 다른 명령은 + 읽기만 한다. +- 레코드 필드의 이름은 경로일 수 있다(`at.x`). 펼친 레코드 안의 레코드다. ## 바이트 배치 @@ -136,6 +149,10 @@ v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): | 95 | BYTE | i64 → b | 한 바이트. 0–255 밖이면 `limit` | | 96 | BYTES_LOAD | → b 또는 t | 이름으로 담은 값 `인자`(0–15 바이트, 16–31 text). 이벤트와 보기의 본문이 시작할 때 비어 있다 | | 97 | BYTES_STORE | b 또는 t → | 이름으로 담은 값 `인자`의 비트 0–4를 그 값으로(비트 8이면 뒤에 덧붙여). 담은 바이트는 이벤트의 몫에 세고, 넘으면 `memory`. text 칸에는 text만 | +| 98 | LIST_VALUE | → l | 이벤트의 리스트 상태 `인자`의 값(항목의 복사, 빈 리스트는 0). 아래 "레코드 안의 리스트" | +| 99 | LIST_BIND | l → | 묶이는 리스트 상태 `인자`(레지스터)가 그 리스트를 읽게 한다 | +| 100 | APPEND_IN | i f… → | 리스트 상태 `인자`의 비트 0–15(이벤트의 리스트)에서 인덱스의 항목, 그 필드(비트 16–31, 리스트 필드)에 담긴 리스트에 항목을 덧붙인다. 그 리스트를 다른 값이 나눠 가지면 먼저 제 것으로 복사한다 | +| 101 | NUM_PARSE | t → n | JSON의 수로 쓴 text의 가장 가까운 수. 그런 text가 아니거나 유한하지 않으면 `limit` | bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이트의 정수다. CONST의 인자 2는 i64 상수(값의 8바이트), NUM_TEXT의 인자 2는 i64의 10진 글자다. LOAD·STORE·지역 칸·항목·FIELD·SEND는 i64를 수처럼 옮긴다. FOLD의 종류에 64(PURE_FOLD_INTEGER)를 |
