diff options
| author | eunhhu <[email protected]> | 2026-10-18 19:30:16 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-18 19:30:16 +0100 |
| commit | b22b8c3f6c5378b47c36370225d7c41d4394bb7c (patch) | |
| tree | d1a3cbf1b0e718f0acafa08ec6741761f08e5dd5 /core | |
| parent | e8b3499289022c8729900a58ed218d961a85fec8 (diff) | |
| download | pure-b22b8c3f6c5378b47c36370225d7c41d4394bb7c.tar.gz pure-b22b8c3f6c5378b47c36370225d7c41d4394bb7c.zip | |
Module format v7, 027's 4b-2: keyed documents gone, a keyed list of records holding bytes doing their work: no collections table, DOC, DOC_SIZE or REPLACE (106 opcodes now 103, those from LEN on moved up), keyed messages or a base revision (pure_app_message takes an input and a payload), no PureData document callbacks (a data host opens states and saves them together), no burst by document key; the header 38 bytes; pure-edit-module gone; pure-serve without /objects, ETag, If-Match and If-None-Match, 412 or an app a thread for a module of documents alone (one without states or outputs runs so); a hole closed: an instruction in no handler's, view's or function's block was never verified yet the load's later passes read its arguments (a damaged module read out of bounds once the header moved what the mutation test damaged), now blanked to CONST 0; checks moved: server_documents (DOCS_SOURCE, keyed byte documents with the app's revision, against a Python model, one and three connection threads, a restart) in place of the parity with editor.pure, pipelining and bursts through kill -9 on its inputs and views, the lent budget on a text view (lending a document's read is server_lent_views' already), clocks and tokens on it; message tests without a document host; the HTTP benchmark's document suite runs the earlier document server and the baselines alone; the count harness builds against either message API (runs.sh build: -DPURE_KEYED_MESSAGES before 4b); counts: b33 against b32 with batches within 0.002% but BFS -0.33%, without batches BFS +0.63%, knapsack +0.52%, join +0.38%; the bank's queries (s32 on a v6 module, s33 on v7) field +0.68%, found +0.44%, large +0.42%, the rest within 0.13%; 008 as v7, 027's 4b-2 as made and 4c designed
Diffstat (limited to 'core')
| -rw-r--r-- | core/module-save.c | 19 | ||||
| -rw-r--r-- | core/module-save.h | 6 | ||||
| -rw-r--r-- | core/module-store.c | 77 | ||||
| -rw-r--r-- | core/module-store.h | 8 | ||||
| -rw-r--r-- | core/module.c | 214 | ||||
| -rw-r--r-- | core/module.h | 81 | ||||
| -rw-r--r-- | core/test-module.c | 523 |
7 files changed, 234 insertions, 694 deletions
diff --git a/core/module-save.c b/core/module-save.c index f87cd24..c70a181 100644 --- a/core/module-save.c +++ b/core/module-save.c @@ -15,8 +15,8 @@ * then its items (module-list.h). The log has an entry for each save that * changed lists: per list changed, {u8 name length, the name, u32le length, * the records of its changes (module-list.h) in its image's fields}. A save - * with such an entry that also commits the tree (numbers, texts, links, - * documents) stores its number there as the state "seq": the entry counts + * with such an entry that also commits the tree (numbers, texts, links) + * stores its number there as the state "seq": the entry counts * only when the tree holds its number (or a later one), so both are kept or * neither. A start * reads each list's image and makes again the changes logged after the save @@ -1059,15 +1059,8 @@ static unsigned char *list_changes(const PureApp *app, size_t *size) { return payload; } -PureResult pure_save_commit(PureApp *app, const PureValue *values, void *const *documents, size_t document_count, - int64_t *revisions) { - if (!pure_save_enabled(app)) { - /* Without a host that saves together, a save has one document at most. */ - PureResult result = PURE_OK; - for (size_t i = 0; i < document_count && result == PURE_OK; ++i) - result = app->data.commit(app->data.context, documents[i], &revisions[i]); - return result; - } +PureResult pure_save_commit(PureApp *app, const PureValue *values) { + if (!pure_save_enabled(app)) return PURE_OK; /* the states live in memory alone */ /* A save that may or may not have been stored: until the app starts again * from what the store has, no save follows it. */ if (app->uncertain) return PURE_CONFLICT; @@ -1125,7 +1118,7 @@ PureResult pure_save_commit(PureApp *app, const PureValue *values, void *const * size_t payload_size = 0; unsigned char *payload = result == PURE_OK ? list_changes(app, &payload_size) : NULL; if (payload_size && !payload) result = PURE_ALLOCATION; - bool tree = count || document_count; + bool tree = count; uint64_t seq = app->seq + 1; if (result == PURE_OK && tree && payload_size && app->log) { result = app->stored_seq ? PURE_OK : open_state(app, "", "seq", &app->stored_seq); @@ -1146,7 +1139,7 @@ PureResult pure_save_commit(PureApp *app, const PureValue *values, void *const * } free(payload); if (result == PURE_OK && tree) { - result = app->data.save(app->data.context, documents, document_count, drafts, count, revisions); + result = app->data.save(app->data.context, drafts, count); /* A commit that may yet be stored leaves the app apart from its store, * whatever the save changed: no save follows until it starts again. * One that failed takes back the logged entry, which is on the device. */ diff --git a/core/module-save.h b/core/module-save.h index 281854b..83ffe60 100644 --- a/core/module-save.h +++ b/core/module-save.h @@ -54,10 +54,8 @@ PureResult pure_save_open_values(PureApp *, const PureModule *m, PureValue *valu * runtime lists kept the numbers heard and the sends: they go to them * (moved: they need images). */ PureResult pure_save_open_links(PureApp *, const PureModule *m, bool *heard_moved, bool *sent_moved); -/* Publish the documents and every state changed since the last save - * (values are the states' numbers); revisions are the documents'. */ -PureResult pure_save_commit(PureApp *, const PureValue *values, void *const *documents, size_t document_count, - int64_t *revisions); +/* Publish every state changed since the last save (values are the states' numbers). */ +PureResult pure_save_commit(PureApp *, const PureValue *values); /* A message's fields laid out as load lays them: in order, the fixed ones * and the variable ones' lengths in the prefix, but the last variable diff --git a/core/module-store.c b/core/module-store.c index 92200af..7ca2dc0 100644 --- a/core/module-store.c +++ b/core/module-store.c @@ -1,92 +1,31 @@ #include "module-store.h" -static PureResult open_document(void *context, uint32_t collection, const char *key, - void **document, int64_t *revision) { - (void)collection; /* v2 modules have one collection: the store's key space */ - PureDraft *draft; - PureResult result = pure_draft_open(context, key, &draft); - if (result != PURE_OK) return result; - pure_draft_hold(draft, true); /* values read in the event stay readable */ - *document = draft; - *revision = pure_draft_revision(draft); - return PURE_OK; -} - -static PureVersion version(void *context, void *document) { - (void)context; - return pure_draft_version(document); -} - -static PureResult read(void *context, void *document, PureVersion at, size_t offset, size_t length, - bool (*visit)(void *, PureSpan), void *visit_context) { - (void)context; - return pure_draft_read_version(document, at, offset, length, visit, visit_context); -} - -static PureResult splice(void *context, void *document, size_t offset, size_t remove, PureSpan insert) { - (void)context; - return pure_draft_splice(document, offset, remove, insert); -} - -static PureResult commit(void *context, void *document, int64_t *revision) { - (void)context; - return pure_draft_commit(document, revision); -} - -static void close_document(void *context, void *document) { - (void)context; - pure_draft_close(document); -} - static PureResult state(void *context, const char *name, PureDraft **out) { return pure_state_open(context, name, out); } -enum { SAVED = PURE_APP_PENDING + 2 * PURE_MODULE_STATES + 1 }; +enum { SAVED = 2 * PURE_MODULE_STATES + 3 }; -/* The documents and the app's changed states in one tree commit. */ -static PureResult save(void *context, void *const *documents, size_t document_count, PureDraft *const *states, - size_t count, int64_t *revisions) { +/* The app's changed states in one tree commit. */ +static PureResult save(void *context, PureDraft *const *states, size_t count) { (void)context; - PureDraft *drafts[SAVED] = {0}; - int64_t all[SAVED] = {0}; - if (document_count > PURE_APP_PENDING || count > SAVED - document_count) return PURE_LIMIT; - for (size_t i = 0; i < document_count; ++i) drafts[i] = documents[i]; - for (size_t i = 0; i < count; ++i) drafts[document_count + i] = states[i]; - PureResult result = pure_draft_commit_all(drafts, document_count + count, all); - for (size_t i = 0; i < document_count; ++i) revisions[i] = all[i]; - return result; + int64_t revisions[SAVED]; + if (count > SAVED) return PURE_LIMIT; + return pure_draft_commit_all(states, count, revisions); } static const char *path_directory(void *context) { return context; } static const char *handle_directory(void *context) { return pure_store_path(context); } -/* Through a store handle: documents and states share its mapping. */ -static PureResult open_handle_document(void *context, uint32_t collection, const char *key, void **document, - int64_t *revision) { - (void)collection; - PureDraft *draft; - PureResult result = pure_store_draft(context, key, &draft); - if (result != PURE_OK) return result; - pure_draft_hold(draft, true); - *document = draft; - *revision = pure_draft_revision(draft); - return PURE_OK; -} - static PureResult handle_state(void *context, const char *name, PureDraft **out) { return pure_store_state(context, name, out); } PureData pure_store_data(PureStore *store) { - return (PureData){.context = store, .open = open_handle_document, .version = version, .read = read, - .splice = splice, .commit = commit, .close = close_document, .state = handle_state, - .save = save, .directory = handle_directory}; + return (PureData){.context = store, .state = handle_state, .save = save, .directory = handle_directory}; } PureData pure_tree_data(const char *path) { - return (PureData){.context = (void *)path, .open = open_document, .version = version, - .read = read, .splice = splice, .commit = commit, .close = close_document, - .state = state, .save = save, .directory = path_directory}; + return (PureData){.context = (void *)path, .state = state, .save = save, .directory = path_directory}; } diff --git a/core/module-store.h b/core/module-store.h index d14ab3d..fd5bc6b 100644 --- a/core/module-store.h +++ b/core/module-store.h @@ -1,11 +1,9 @@ #ifndef PURE_MODULE_STORE_H #define PURE_MODULE_STORE_H #include "module.h" -/* Keyed module collections and the app's states on the selected tree store: - * one event opens the message key as a held draft and commits it together - * with the states the event changed. The path outlives the app. */ +/* The app's states on the selected tree store, each save one commit. The path outlives the app. */ PureData pure_tree_data(const char *path); -/* The same through a store handle (state.h): opening a document or state - * checks only whether the store changed. The handle outlives the app. */ +/* The same through a store handle (state.h): opening a state checks only whether the store changed. The handle outlives + * the app. */ PureData pure_store_data(PureStore *); #endif diff --git a/core/module.c b/core/module.c index b8782f3..5ebecea 100644 --- a/core/module.c +++ b/core/module.c @@ -10,7 +10,7 @@ #include <string.h> enum { FUNCTION_BYTES = 12, SIGNATURE_BYTES = 4 }; -enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES = 8, +enum { STATE_BYTES = 16, INPUT_BYTES = 16, FIELD_BYTES = 8, 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 @@ -19,25 +19,25 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES = * 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_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 }; +enum { FROM_PAYLOAD, 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). */ /* IN_LOCALS: a handler's locals, read in its walks and the folds in them. */ /* IN_COMPUTE: what a view's body may do too (018): loops, locals, and changes to an event's lists alone. */ /* IN_FUNCTION: a function's code (027's 2c), a handler's but for a message. */ -enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32, IN_OUTER = 64, IN_ARGUMENTS = 128, +enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_TEXT = 16, IN_ROW = 32, IN_OUTER = 64, IN_ARGUMENTS = 128, IN_LOCALS = 256, IN_COMPUTE = 512, IN_FUNCTION = 1024 }; enum { READ = IN_VIEW | IN_HANDLER }; /* What an argument names: nothing (it is 0), a state of one type (A_STATE + - * the type), a list and one of its record's fields, a message field, the - * collection, a constant, a unit, the column's row, a branch target, a stack + * the type), a list and one of its record's fields, a message field, a + * constant, a unit, the column's row, a branch target, a stack * depth, a fold, a relation, a keyed list, an output, the row of the column * a fold runs in, a number, bool or i64 state (A_VALUE), or the value's type * (A_TYPE: 0 a number, 1 a bool, 2 an i64), how I64_BITS computes (A_BITS), * what a position is (A_POSITION: 0 a number, 1 an i64), what CONCAT joins * (A_JOINED: 0 texts, 1 bytes), how NUM_TEXT writes (A_FORMAT) or the value * held by name (A_HELD), or a function (A_FUNCTION: CALL's, and RETURN's own). */ -enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_COLLECTION, A_CONSTANT, A_UNIT, +enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_CONSTANT, A_UNIT, A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_TYPE, A_LOOP, A_BITS, A_POSITION, A_JOINED, A_FORMAT, A_HELD, A_FUNCTION }; /* A fold's block is verified as a column that leaves one bool (count, first) @@ -56,7 +56,7 @@ enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK, STEPS, STEPS_WALK, FUNC * O_REF: the text or list of the argument's cursor or index state. O_LIST: * the list of an item argument. */ enum { O_ANY, O_OPERAND, O_ARG, O_REF, O_LIST }; -static const unsigned char magic[8] = {'P','U','R','E','M','O','D','6'}; +static const unsigned char magic[8] = {'P','U','R','E','M','O','D','7'}; /* Operand types popped (bottom to top), the type pushed, where allowed, what * the argument names and the text or list worked on. */ @@ -77,10 +77,7 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi [PURE_OP_JZ] = {1, {T_BOOL}, 0, READ, A_TARGET, O_ANY}, [PURE_OP_JMP] = {0, {0}, 0, READ, A_TARGET, O_ANY}, [PURE_OP_FIELD] = {0, {0}, T_FIELD, IN_MESSAGE | IN_ARGUMENTS, A_FIELD, O_ANY}, - [PURE_OP_DOC] = {0, {0}, T_BYTES, IN_KEYED, A_COLLECTION, O_ANY}, - [PURE_OP_DOC_SIZE] = {0, {0}, T_NUM, IN_KEYED, A_COLLECTION, O_ANY}, [PURE_OP_LEN] = {1, {T_BYTES}, T_NUM, READ, A_NONE, O_ANY}, - [PURE_OP_REPLACE] = {3, {T_NUM, T_NUM, T_BYTES}, 0, IN_KEYED, A_COLLECTION, O_ANY}, [PURE_OP_REPLY] = {1, {T_BYTES}, 0, IN_MESSAGE, A_NONE, O_ANY}, [PURE_OP_REQUIRE] = {1, {T_BOOL}, 0, IN_HANDLER, A_NONE, O_ANY}, [PURE_OP_TEXT_CONST] = {0, {0}, T_TEXT, READ, A_CONSTANT, O_ANY}, @@ -309,18 +306,6 @@ PURE_COLD static bool key_name(const char *key) { return false; } -/* A document key: 1..64 bytes of [A-Za-z0-9_-], as every Pure store accepts. */ -static bool document_key(const char *key) { - size_t length = key ? strlen(key) : 0; - if (!length || length > 64) return false; - for (size_t i = 0; i < length; ++i) { - char c = key[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || - c == '-' || c == '_')) return false; - } - return true; -} - /* Strings are [u8 length][bytes]; a name is 1..31 bytes: an ASCII identifier, * a path of identifiers joined by dots (a message field inside a group, as * user.name), or any printable ASCII (a key's name). */ @@ -438,7 +423,6 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg case A_ITEM: return state_of(m, list_of(arg), PURE_STATE_LIST) && item_field(arg) < m->record[m->state_ref[list_of(arg)]].count; case A_FIELD: return arg < shape->field_count && m->field[shape->field_start + arg].type != PURE_FIELD_ROWS; - case A_COLLECTION: return arg < m->collections; case A_CONSTANT: return arg < m->constants; case A_UNIT: return arg <= PURE_UNIT_LINE; case A_ROW: return (int)arg == row; @@ -519,7 +503,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i unsigned where = steps ? IN_VIEW | IN_COMPUTE | (view == STEPS_WALK ? IN_ROW : 0) : !handler ? IN_VIEW | (row >= 0 ? IN_ROW : 0) | (folding && outer >= 0 ? IN_OUTER : 0) | (body ? IN_COMPUTE : 0) : IN_HANDLER | (view == WALK ? IN_ROW : 0); - if (shape && handler && !steps && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE | (shape->keyed ? IN_KEYED : 0); + if (shape && handler && !steps && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE; if (shape && handler && !steps && shape->kind == PURE_KIND_TEXT) where |= IN_TEXT; if (shape && (!handler || steps) && (shape->kind == PURE_KIND_MESSAGE || shape->kind == PURE_KIND_ARGUMENTS)) where |= IN_ARGUMENTS; @@ -568,7 +552,6 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i if (view == FILTER) REFUSE("filter with an effect"); if (folding) REFUSE("fold with an effect"); if (!handler || steps) REFUSE("view with an effect"); - if (allowed & IN_KEYED) REFUSE("document operation outside a keyed message handler"); if (allowed & IN_TEXT) REFUSE("text input outside a text handler"); REFUSE("message operation outside a message handler"); } @@ -1602,7 +1585,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if (error_size) error[0] = 0; pure_module_release(out); memset(out, 0, sizeof(*out)); - if (!p || size < PURE_MODULE_HEADER || memcmp(p, magic, sizeof(magic))) REFUSE("not a Pure module v6", 0); + if (!p || size < PURE_MODULE_HEADER || memcmp(p, magic, sizeof(magic))) REFUSE("not a Pure module v7", 0); out->states = (uint16_t)read16(p + 8); out->inputs = (uint16_t)read16(p + 10); out->views = (uint16_t)read16(p + 12); @@ -1610,24 +1593,21 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule out->code_count = read32(p + 16); uint32_t strings = read32(p + 20); 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); - out->record_fields = (uint16_t)read16(p + 36); - out->functions = (uint16_t)read16(p + 38); + out->constants = (uint16_t)read16(p + 26); + out->records = (uint16_t)read16(p + 28); + uint32_t constant_bytes = read32(p + 30); + out->record_fields = (uint16_t)read16(p + 34); + out->functions = (uint16_t)read16(p + 36); 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->fields > PURE_MODULE_FIELDS || out->constants > PURE_MODULE_CONSTANTS || constant_bytes > PURE_MODULE_CONSTANT_BYTES || out->records > PURE_MODULE_RECORDS || out->record_fields > PURE_MODULE_RECORD_FIELDS || out->functions > PURE_MODULE_FUNCTIONS) { result = PURE_LIMIT; REFUSE("module exceeds a limit", 8); } - size_t functions_at = PURE_MODULE_HEADER + (size_t)out->states * STATE_BYTES + - (size_t)out->collections * COLLECTION_BYTES + (size_t)out->inputs * INPUT_BYTES + + size_t functions_at = PURE_MODULE_HEADER + (size_t)out->states * STATE_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; @@ -1695,24 +1675,19 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule out->state_key[i] = (uint8_t)key; } } - for (uint32_t i = 0; i < out->collections; ++i, at += COLLECTION_BYTES) { - if (!name_at(text, strings, read32(at), NAME_IDENTIFIER, out->collection_names[i])) REFUSE("collection name", i); - if (read32(at + 4) != 1 || read32(at + 8) != 1) REFUSE("collection must hold bytes by key", i); - } uint32_t next_field = 0; const unsigned char *fields = at + (size_t)out->inputs * INPUT_BYTES; for (uint32_t i = 0; i < out->inputs; ++i, at += INPUT_BYTES) { PureInputShape *shape = &out->input[i]; shape->kind = at[4]; - shape->keyed = at[5]; shape->field_start = (uint16_t)read16(at + 12); shape->field_count = (uint16_t)read16(at + 14); /* Arguments bear their view's name (view.args); other inputs are identifiers. */ if (!name_at(text, strings, read32(at), shape->kind == PURE_KIND_ARGUMENTS ? NAME_PATH : NAME_IDENTIFIER, out->input_names[i])) REFUSE("input name", i); - if (read16(at + 6) || at[5] > 1) REFUSE("input flags", i); + if (read16(at + 6) || at[5]) REFUSE("input flags", i); if (shape->kind == PURE_KIND_KEY) { - if (shape->keyed || shape->field_start || shape->field_count || + if (shape->field_start || shape->field_count || !name_at(text, strings, read32(at + 8), NAME_PRINTABLE, out->input_keys[i]) || !key_name(out->input_keys[i])) REFUSE("input key", i); for (uint32_t k = 0; k < i; ++k) @@ -1721,19 +1696,18 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule continue; } if (shape->kind == PURE_KIND_TEXT) { - if (shape->keyed || shape->field_start || shape->field_count || read32(at + 8)) - REFUSE("text input shape", i); + if (shape->field_start || shape->field_count || read32(at + 8)) REFUSE("text input shape", i); continue; } if (shape->kind == PURE_KIND_TIMER) { shape->period = read32(at + 8); - if (shape->keyed || shape->field_start || shape->field_count || !shape->period || + if (shape->field_start || shape->field_count || !shape->period || shape->period > PURE_TIMER_LONGEST) REFUSE("timer input", i); continue; } bool arguments = shape->kind == PURE_KIND_ARGUMENTS; if ((shape->kind != PURE_KIND_MESSAGE && shape->kind != PURE_KIND_OUTPUT && !arguments) || (!arguments && read32(at + 8)) || - ((shape->kind == PURE_KIND_OUTPUT || arguments) && shape->keyed) || shape->field_start != next_field || + shape->field_start != next_field || shape->field_count > out->fields - next_field) REFUSE("message shape", i); shape->view = arguments ? read32(at + 8) : 0; for (uint32_t f = 0; f < shape->field_count; ++f) { @@ -1892,13 +1866,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if (at[1] || at[2] || at[3]) REFUSE("instruction padding", i); out->code[i] = (PureOp){.op = at[0], .arg = read32(at + 4), .value = read_double(at + 8)}; } - /* 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]; + /* States, records, inputs and views share one namespace. */ + const char *names[PURE_MODULE_STATES + PURE_MODULE_RECORDS + 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) if (!out->view_table[i]) names[count++] = out->view_names[i]; for (size_t i = 0; i < count; ++i) @@ -1954,6 +1926,20 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event, true, 0, 0, -1, &bad))) goto refused; if (!locals_agree(out, out->handler[i].code)) { why = "local used as two things"; goto refused; } } + /* An instruction in no handler's, view's or function's block never runs and was never verified: it becomes CONST 0, + * so the passes below, which read every instruction's arguments, read only what was verified. */ + unsigned char *held = calloc(out->code_count + 1, 1); + if (!held) { + result = PURE_ALLOCATION; + REFUSE("out of memory", 0); + } + for (uint32_t i = 0; i < out->handlers; ++i) memset(held + out->handler[i].code.start, 1, out->handler[i].code.length); + for (uint32_t i = 0; i < out->functions; ++i) memset(held + out->function[i].code.start, 1, out->function[i].code.length); + for (uint32_t i = 0; i < out->views; ++i) + if (out->view_code[i].length) memset(held + out->view_code[i].start, 1, out->view_code[i].length); + for (uint32_t pc = 0; pc < out->code_count; ++pc) + if (!held[pc]) out->code[pc] = (PureOp){.op = PURE_OP_CONST}; + free(held); for (uint32_t i = 0; i < out->views; ++i) { int given = (int)out->view_arguments[i] - 1; plan_folds(out, out->view_code[i], given); @@ -2264,12 +2250,9 @@ static PureResult visit_value(const PureApp *app, const PureArena *arena, PureBy case FROM_CONSTANT: direct = app->module->constant_data + v.offset; break; case FROM_ARENA: direct = arena->bytes + v.offset; break; case FROM_HELD: direct = app->held[v.owner].data + v.offset; break; - case FROM_TEXT: + default: /* FROM_TEXT */ return pure_draft_read_version(app->texts[v.owner], v.version, (size_t)v.offset, (size_t)v.length, visit, context); - default: - return app->data.read(app->data.context, app->document, v.version, (size_t)v.offset, - (size_t)v.length, visit, context); } visit(context, (PureSpan){direct, (size_t)v.length}); return PURE_OK; @@ -3719,31 +3702,7 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp ev->spans[arg][1]}; NEXT; } - case PURE_OP_DOC: case PURE_OP_DOC_SIZE: { - if (op->op == PURE_OP_DOC && !ev->exists) { kind = PURE_FAULT_NOT_FOUND; FAILED; } - PureVersion version = app->data.version(app->data.context, app->document); - if (op->op == PURE_OP_DOC) stack[sp++].bytes = (PureBytes){FROM_DOCUMENT, 0, version, 0, version.bytes}; - else stack[sp++].num = (double)version.bytes; - NEXT; - } case PURE_OP_LEN: case PURE_OP_TEXT_BYTES: top->num = (double)top->bytes.length; NEXT; - case PURE_OP_REPLACE: { - PureBytes insert = stack[--sp].bytes; - double last = stack[--sp].num, first = stack[--sp].num; - double size = (double)app->data.version(app->data.context, app->document).bytes; - if (first != floor(first) || last != floor(last) || first < 0 || last < first || last > size) { - kind = PURE_FAULT_LIMIT; - FAILED; - } - PureSpan span; - size_t mark = arena->used; - if ((status = contiguous(app, arena, insert, &span)) == PURE_OK) - status = app->data.splice(app->data.context, app->document, (size_t)first, (size_t)(last - first), span); - arena->used = mark; - if (status != PURE_OK) REFUSED; - ev->modified = ev->exists = true; - NEXT; - } /* A reply outlives the arena mark of later statements: keep it by value. */ case PURE_OP_REPLY: { /* by value; a value held by name may change after it, so its bytes go to the arena */ PureBytes reply = stack[--sp].bytes; @@ -4773,25 +4732,8 @@ PURE_COLD static PureList *new_list(const PureModule *m, uint32_t record, bool k return pure_list_new(r->count, kinds); } -/* Where key is in the pending index: its slot, or the empty one it would take. */ -static unsigned pending_slot(const PureApp *app, const char *key) { - uint32_t hash = 2166136261u; /* FNV-1a */ - for (const char *c = key; *c; ++c) hash = (hash ^ (unsigned char)*c) * 16777619u; - unsigned mask = 2 * PURE_APP_PENDING - 1, slot = hash & mask; - while (app->pending_index[slot] && strcmp(app->pending[app->pending_index[slot] - 1].key, key)) slot = (slot + 1) & mask; - return slot; -} - -bool pure_app_pending(const PureApp *app, const char *key) { - return app->pending_count && app->pending_index[pending_slot(app, key)]; -} - void pure_app_finish(PureApp *app) { - /* A document in the unsaved burst stays open until the save. */ - if (app->document && !app->document_pending) app->data.close(app->data.context, app->document); - app->document_pending = false; free(app->copied); - app->document = NULL; app->copied = NULL; app->has_reply = false; app->payload = (PureSpan){0}; @@ -4820,14 +4762,8 @@ static void open_burst(PureApp *app) { app->open = true; } -/* End a burst, keeping it (saved) or undoing all of it. The documents it held - * close, except the current event's, which pure_app_finish closes. */ +/* End a burst, keeping it (saved) or undoing all of it. */ static void close_burst(PureApp *app, bool keep) { - for (unsigned i = 0; i < app->pending_count; ++i) - if (app->pending[i].document != app->document) app->data.close(app->data.context, app->pending[i].document); - if (app->pending_count) memset(app->pending_index, 0, sizeof(app->pending_index)); - app->pending_count = 0; - app->document_pending = false; if (!app->open) return; const PureModule *m = app->module; for (uint32_t i = 0; i < m->states; ++i) { @@ -4866,12 +4802,7 @@ static void settle_links(PureApp *app, bool saved) { } PureResult pure_app_save(PureApp *app) { - void *documents[PURE_APP_PENDING]; - int64_t revisions[PURE_APP_PENDING]; - size_t count = 0; - for (unsigned i = 0; i < app->pending_count; ++i) - if (app->pending[i].edited) documents[count++] = app->pending[i].document; - PureResult result = pure_save_commit(app, app->values, documents, count, revisions); + PureResult result = pure_save_commit(app, app->values); settle_links(app, result == PURE_OK); close_burst(app, result == PURE_OK); /* Images due are written now, unless the host writes them on another thread. */ @@ -4988,7 +4919,6 @@ void pure_app_stop(PureApp *app) { pure_draft_close(app->stored_seq); pure_save_close_start(app); pure_log_close(app->log); - free(app->pending); pure_undo_free(&app->undo); for (unsigned i = 0; i < app->row_tables; ++i) free(app->rows[i].items); free(app->rows); @@ -5105,8 +5035,7 @@ size_t pure_app_state_bytes(const PureApp *app) { /* One event: every handler for it runs in module order against pending * states, all or nothing. A key, text or message event saves before it * returns unless saving is deferred; a frame leaves that to the next event - * (frames come 60 times a second). A message's document joins the burst. - * With a memory budget, an event that leaves the states over it, and + * (frames come 60 times a second). With a memory budget, an event that leaves the states over it, and * holding more than before, fails. */ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { const PureModule *m = app->module; @@ -5131,13 +5060,8 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { if (m->handler[i].event == event) result = charge(app, m->handler[i].code.length) ? run(m, pending, m->handler[i].code, ev, NULL) : fault(app, PURE_FAULT_WORK, m->handler[i].code.start); - /* An edit needs the revision it was made against. A save rebases an - * edited document and releasing texts may reclaim earlier text versions: - * keep such a reply by copying it first. */ - if (result == PURE_OK && ev->modified && ev->base < 0) result = PURE_INVALID; - if (result == PURE_OK && app->has_reply && - ((app->reply.source == FROM_DOCUMENT && ev->modified) || app->reply.source == FROM_TEXT)) - result = keep_reply(app); + /* Releasing texts may reclaim earlier text versions: keep a text's reply by copying it first. */ + if (result == PURE_OK && app->has_reply && app->reply.source == FROM_TEXT) result = keep_reply(app); /* A delivery's number is heard with its changes, and an answer's sends go with it. */ if (result == PURE_OK && app->delivering.pending) result = hear(app, app->delivering.source, app->delivering.seq); if (result == PURE_OK && app->delivering.pending && app->delivering.release) @@ -5160,14 +5084,6 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { ++app->events; ++app->changes; if (!event) ++app->ticks; - if (app->document) { - app->pending_index[pending_slot(app, ev->key)] = (uint16_t)(app->pending_count + 1); /* not there: a key joins once */ - app->pending[app->pending_count].document = app->document; - app->pending[app->pending_count].edited = ev->modified; - memcpy(app->pending[app->pending_count++].key, ev->key, strlen(ev->key) + 1); - app->document_pending = true; - app->revision += ev->modified; /* a save moves an edited document one revision */ - } app->delivering.pending = false; return event && !app->deferred ? pure_app_save(app) : PURE_OK; } @@ -5279,38 +5195,16 @@ PureResult pure_app_arguments(PureApp *app, uint32_t view, PureSpan payload, con /* A message whose variable fields are where placed says (start and size in * the payload), or, without it, one after another after the prefix. */ -static PureResult message(PureApp *app, uint32_t input, const char *key, int64_t base, PureSpan payload, - const uint32_t *placed) { +static PureResult message(PureApp *app, uint32_t input, PureSpan payload, const uint32_t *placed) { pure_app_finish(app); const PureModule *m = app->module; app->fault = PURE_FAULT_NONE; app->fault_pc = UINT32_MAX; - app->revision = 0; - if (input >= m->inputs || m->input[input].kind != PURE_KIND_MESSAGE || base < -1 || - (payload.size && !payload.data) || (m->input[input].keyed ? !document_key(key) : key != NULL)) - return PURE_INVALID; + if (input >= m->inputs || m->input[input].kind != PURE_KIND_MESSAGE || (payload.size && !payload.data)) return PURE_INVALID; const PureInputShape *shape = &m->input[input]; uint32_t spans[PURE_MODULE_FIELDS][2]; PureFault wrong = place_fields(m, shape, payload, placed, spans); if (wrong != PURE_FAULT_NONE) return fault(app, wrong, UINT32_MAX); - if (shape->keyed && m->collections) { - if (!app->data.open) return PURE_INVALID; - /* A key joins a burst once, so its revision moves once per save. */ - if (app->pending_count == PURE_APP_PENDING || pure_app_pending(app, key)) { - PureResult saved = pure_app_save(app); - if (saved != PURE_OK) return saved; - } - if (app->pending_count == app->pending_capacity) { - unsigned capacity = app->pending_capacity ? 2 * app->pending_capacity : 16; - struct PurePending *grown = realloc(app->pending, capacity * sizeof(*grown)); - if (!grown) return PURE_ALLOCATION; - app->pending = grown; - app->pending_capacity = capacity; - } - PureResult opened = app->data.open(app->data.context, 0, key, &app->document, &app->revision); - if (opened != PURE_OK) { app->document = NULL; return opened; } - if (base >= 0 && base != app->revision) { pure_app_finish(app); return PURE_CONFLICT; } - } /* Its rows fill their lists (emptied when the event ends); rows that are not whole fail it. Rows whose records hold * lists (027's 4a) read those into the event's lists as a kept list's cell is read, in the event's lists begun anew. */ bool nested = false; @@ -5335,20 +5229,16 @@ static PureResult message(PureApp *app, uint32_t input, const char *key, int64_t return filled == PURE_ALLOCATION ? filled : fault(app, PURE_FAULT_LIMIT, UINT32_MAX); } app->payload = payload; - Event ev = {.app = app, .shape = shape, .key = key, .base = base, .arena = &app->arena, .exists = app->revision > 0, - .nested = nested, .spans = spans}; + Event ev = {.app = app, .shape = shape, .arena = &app->arena, .nested = nested, .spans = spans}; PureResult result = dispatch(app, 1 + 2 * input, &ev); if (result != PURE_OK) pure_app_finish(app); return result; } -PureResult pure_app_message(PureApp *app, uint32_t input, const char *key, int64_t base, PureSpan payload) { - return message(app, input, key, base, payload, NULL); -} +PureResult pure_app_message(PureApp *app, uint32_t input, PureSpan payload) { return message(app, input, payload, NULL); } -PureResult pure_app_message_fields(PureApp *app, uint32_t input, const char *key, int64_t base, PureSpan payload, - const uint32_t *placed) { - return message(app, input, key, base, payload, placed); +PureResult pure_app_message_fields(PureApp *app, uint32_t input, PureSpan payload, const uint32_t *placed) { + return message(app, input, payload, placed); } /* The outbox's item at: its output (the module's, by name), number and payload. */ @@ -5388,7 +5278,7 @@ static PureResult deliver(PureApp *app, uint32_t input, uint32_t release, const size_t length = source ? strlen(source) : 0; app->duplicate = false; /* Kept states without a directory would not keep the numbers heard. */ - if (input >= m->inputs || m->input[input].kind != PURE_KIND_MESSAGE || m->input[input].keyed || !length || + if (input >= m->inputs || m->input[input].kind != PURE_KIND_MESSAGE || !length || length > PURE_SOURCE || !seq || seq > PURE_SEQ_MOST || !app->heard || (pure_save_enabled(app) && !app->log)) return PURE_INVALID; for (size_t i = 0; i < length; ++i) @@ -5402,7 +5292,7 @@ static PureResult deliver(PureApp *app, uint32_t input, uint32_t release, const app->delivering.seq = seq; app->delivering.release = release; app->delivering.pending = true; - PureResult result = message(app, input, NULL, -1, payload, placed); + PureResult result = message(app, input, payload, placed); if (!app->delivering.pending) return result; /* Not applied. Refused by the app (a fault), the message was delivered * all the same: its number is heard, saved like a key event's changes. @@ -6305,7 +6195,7 @@ PURE_COLD PureResult pure_app_replace(PureApp *app, const PureModule *module) { drop_nested(app); drop_spares(app); pure_app_finish(app); - if (app->open || app->pending_count) { + if (app->open) { PureResult saved = pure_app_save(app); /* the burst belongs to the module that made it */ if (saved != PURE_OK) return saved; } diff --git a/core/module.h b/core/module.h index d23cb49..d155b41 100644 --- a/core/module.h +++ b/core/module.h @@ -8,7 +8,7 @@ #include <stddef.h> #include <stdint.h> -/* Pure module format v6 (docs/design/008-module-format.md). A toolchain such +/* Pure module format v7 (docs/design/008-module-format.md). A toolchain such * as aup produces it; Pure verifies it completely before anything executes: * operand types (including which text a cursor and which list an index * belongs to), stack heights and the stack at every join, forward-only @@ -16,10 +16,10 @@ enum { PURE_MODULE_STATES = 32, PURE_MODULE_INPUTS = 32, PURE_MODULE_VIEWS = 64, PURE_MODULE_HANDLERS = 64, PURE_MODULE_CODE = 65535, PURE_MODULE_STACK = 64, - PURE_MODULE_NAME = 32, PURE_MODULE_STRINGS = 32768, PURE_MODULE_HEADER = 40, - PURE_MODULE_FIELDS = 64, PURE_MODULE_COLLECTIONS = 1, PURE_MODULE_CONSTANTS = 4096, + PURE_MODULE_NAME = 32, PURE_MODULE_STRINGS = 32768, PURE_MODULE_HEADER = 38, + PURE_MODULE_FIELDS = 64, PURE_MODULE_CONSTANTS = 4096, PURE_MODULE_CONSTANT_BYTES = 1 << 20, PURE_MODULE_RECORDS = 16, PURE_MODULE_RECORD_FIELDS = 128, - PURE_RECORD_FIELDS = 16, PURE_MODULE_JOINS = 8, PURE_APP_PENDING = 256 + PURE_RECORD_FIELDS = 16, PURE_MODULE_JOINS = 8 }; typedef enum { @@ -27,7 +27,7 @@ typedef enum { PURE_OP_DIV, PURE_OP_NEG, PURE_OP_LT, PURE_OP_LE, PURE_OP_GT, PURE_OP_GE, PURE_OP_EQ, PURE_OP_NE, PURE_OP_AND, PURE_OP_OR, PURE_OP_NOT, PURE_OP_MIN, PURE_OP_MAX, PURE_OP_CLAMP, PURE_OP_STORE, PURE_OP_JZ, PURE_OP_JMP, - PURE_OP_FIELD, PURE_OP_DOC, PURE_OP_DOC_SIZE, PURE_OP_LEN, PURE_OP_REPLACE, + PURE_OP_FIELD, PURE_OP_LEN, PURE_OP_REPLY, PURE_OP_REQUIRE, PURE_OP_TEXT_CONST, PURE_OP_TEXT_LOAD, PURE_OP_TEXT_INPUT, PURE_OP_NUM_TEXT, PURE_OP_CONCAT, PURE_OP_CHARS, PURE_OP_LINES, PURE_OP_TEXT_BYTES, @@ -126,8 +126,8 @@ enum { PURE_FORMAT_WIDTH = 8, PURE_FORMAT_ZEROS = 1 << 16, PURE_FORMAT_FORM = 17 * locals (0 as it begins), and its own of what the function's code names: * values held by name (empty), an event's lists (empty) and registers (bound * to none); the caller's are as they were when the call returns. A function - * runs as a handler's code does but for a message (no field, document or - * reply); a handler and the functions run CALL (not a view), and a walk around + * runs as a handler's code does but for a message (no field or reply); a + * handler and the functions run CALL (not a view), and a walk around * a call keeps to what the callee changes as it keeps to its own block's * changes. Calls nest as deep as the event's room allows: each counts its * frame's bytes there (MEMORY past it) and its block's length as work. @@ -308,7 +308,6 @@ typedef struct { typedef struct { char name[PURE_MODULE_NAME]; uint8_t type; uint16_t rows; uint32_t offset; } PureField; typedef struct { uint8_t kind; - bool keyed; uint16_t field_start, field_count; uint32_t prefix; /* fixed payload bytes before the variable fields */ uint32_t period; /* a timer's, in milliseconds */ @@ -339,12 +338,11 @@ typedef struct { uint8_t lower_relation, upper_relation; PureBlock lower, upper; _Static_assert(PURE_MODULE_STATES <= 32, "a state is a bit of PureApp.counted"); typedef struct { - uint16_t states, inputs, views, handlers, fields, collections, constants, records, record_fields; + uint16_t states, inputs, views, handlers, fields, constants, records, record_fields; uint32_t code_count; char state_names[PURE_MODULE_STATES][PURE_MODULE_NAME]; char input_names[PURE_MODULE_INPUTS][PURE_MODULE_NAME]; char view_names[PURE_MODULE_VIEWS][PURE_MODULE_NAME]; - char collection_names[PURE_MODULE_COLLECTIONS][PURE_MODULE_NAME]; char input_keys[PURE_MODULE_INPUTS][PURE_MODULE_NAME]; uint8_t state_type[PURE_MODULE_STATES]; uint16_t state_ref[PURE_MODULE_STATES]; /* text: constant; cursor: text; list: record; index: list */ @@ -449,27 +447,13 @@ int pure_module_position(const PureModule *, uint32_t view); * state), or -1: a host may lend its stored extents instead of copying. */ int pure_module_text_state(const PureModule *, uint32_t view); -/* Keyed collections live in a data host (for example the tree store). One - * event opens at most one document, the message's key, at its current - * version; edits stay in that working version until the event commits it. - * A version's bytes field is the value's size. */ +/* A data host keeps the app's states (for example the tree store); without one (all of it NULL) they live in memory + * alone. state opens a runtime-owned value by name as a draft (a missing one is empty at revision 0); save publishes + * the given drafts together, all or nothing. */ typedef struct { void *context; - PureResult (*open)(void *context, uint32_t collection, const char *key, - void **document, int64_t *revision); - PureVersion (*version)(void *context, void *document); - PureResult (*read)(void *context, void *document, PureVersion, size_t offset, size_t length, - bool (*visit)(void *, PureSpan), void *visit_context); - PureResult (*splice)(void *context, void *document, size_t offset, size_t remove, PureSpan insert); - PureResult (*commit)(void *context, void *document, int64_t *revision); - void (*close)(void *context, void *document); - /* Optional: the host keeps the app's states. state opens a runtime-owned - * value by name as a draft (a missing one is empty at revision 0); save - * publishes the given documents and state drafts together, all or - * nothing, and names each document's new revision. */ PureResult (*state)(void *context, const char *name, PureDraft **out); - PureResult (*save)(void *context, void *const *documents, size_t document_count, PureDraft *const *states, - size_t count, int64_t *revisions); + PureResult (*save)(void *context, PureDraft *const *states, size_t count); /* Optional, with state and save: the directory where the app's lists are * kept, their images and the log of their changes (core/log.h); without * one, list states are not stored. */ @@ -477,7 +461,7 @@ typedef struct { } PureData; /* A byte or text value inside one event or view: a range of the payload, a - * document or text version, a module constant, a copy or the event's arena. */ + * text version, a module constant, a copy or the event's arena. */ typedef struct { uint8_t source; uint16_t owner; PureVersion version; uint64_t offset, length; } PureBytes; typedef struct { unsigned char *bytes; size_t used, capacity; } PureArena; @@ -487,13 +471,11 @@ typedef struct { unsigned char *bytes; size_t used, capacity; } PureArena; * (or 0 in an empty list). Every event is atomic: on failure nothing changes. * * Saving. When the data host keeps states the app starts from the stored - * ones. A save publishes, in one commit, the documents edited by messages and - * the states changed since the last save; if it fails, every event since the - * last save is undone. By default each key, text and message event saves - * before it returns (a frame waits for the next one). Deferred, events apply - * at once and a host saves a burst with one commit: an event is durable once - * a later save succeeds, and each key's document joins a burst once (a second - * message for it, or one past PURE_APP_PENDING keys, saves the burst first). */ + * ones. A save publishes, in one commit, the states changed since the last + * save; if it fails, every event since the last save is undone. By default + * each key, text and message event saves before it returns (a frame waits for + * the next one). Deferred, events apply at once and a host saves a burst with + * one commit: an event is durable once a later save succeeds. */ /* The runtime's lists' places among the stored lists' records (PureApp): * the numbers heard, and the outbox. */ enum { PURE_LIST_HEARD = PURE_MODULE_STATES, PURE_LIST_OUTBOX, PURE_LISTS_STORED }; @@ -521,20 +503,12 @@ typedef struct { bool uncertain; /* a save may or may not be stored: none follows until a start */ PureDraft *stored_values; /* numbers, cursors and indices, when kept */ PureVersion saved[PURE_MODULE_STATES]; /* text and list versions at the last save */ - /* Events since the last save: what a failed save returns to, and the - * documents messages opened (held until the save). */ + /* Events since the last save: what a failed save returns to. */ bool deferred, open; PureValue durable_values[PURE_MODULE_STATES]; PureVersion durable_texts[PURE_MODULE_STATES]; size_t durable_lists[PURE_MODULE_STATES]; /* each stored list's journal length */ uint64_t durable_ticks, durable_events; - struct PurePending { void *document; char key[65]; bool edited; } *pending; /* grows to PURE_APP_PENDING */ - unsigned pending_count, pending_capacity; - /* The pending documents by key, so a message finds its key in one look: - * open addressing over the key's hash, a slot holding one more than the - * document's place in pending (0: none). */ - uint16_t pending_index[2 * PURE_APP_PENDING]; - bool document_pending; /* the current event's document is among them */ PureFault fault; uint32_t fault_pc; size_t memory; /* the budget of what the states hold (0: none): pure_app_memory */ @@ -553,12 +527,10 @@ typedef struct { struct PureRows { uint32_t table; uint64_t changes, given; size_t *items, count, capacity; PureFault fault; } *rows; /* a filtered table's items */ unsigned row_tables; /* Current message or text event, kept until pure_app_finish. */ - void *document; PureSpan payload; bool has_reply; PureBytes reply; unsigned char *copied; - int64_t revision; PureArena arena, view_arena; /* Links (runtime-owned, saved with the states): the outbox, messages sent * on outputs and not trimmed, in the order sent, a list of {output (its @@ -612,11 +584,8 @@ PureResult pure_app_start(PureApp *, const PureModule *, PureData data); /* Publish every change since the last save in one commit, or undo them all. */ PureResult pure_app_save(PureApp *); /* Defer saving to pure_app_save (false: save after each event). Needs a data - * host that saves documents together (PureData.save). */ + * host that saves together (PureData.save). */ bool pure_app_defer(PureApp *, bool deferred); -/* Whether key's document is in the unsaved burst: a message for it would - * save the burst first. */ -bool pure_app_pending(const PureApp *, const char *key); /* Leaves unsaved frame changes unsaved: call pure_app_save first to keep them. */ void pure_app_stop(PureApp *); PureResult pure_app_frame(PureApp *); @@ -629,18 +598,14 @@ PureResult pure_app_input(PureApp *, uint32_t input, PureEdge); PureResult pure_app_point(PureApp *, uint32_t state, size_t position); /* Deliver committed text (for example from an input method). Not UTF-8: invalid. */ PureResult pure_app_text(PureApp *, uint32_t input, PureSpan text); -/* Deliver a message. base is the revision the sender edited, or -1 for none; - * a handler that edits the document needs one. payload stays alive until - * pure_app_finish. On return app->revision is the document's revision - * (deferred: the revision the next save gives it). */ -PureResult pure_app_message(PureApp *, uint32_t input, const char *key, int64_t base, PureSpan payload); +/* Deliver a message. payload stays alive until pure_app_finish. */ +PureResult pure_app_message(PureApp *, uint32_t input, PureSpan payload); /* The same, with the variable fields where the host found them: field f * starts at placed[2f] in payload and has placed[2f + 1] bytes (entries of * fixed fields are not read, nor are the variable fields' lengths in the * prefix). A host that parsed a request in place passes the fields' bytes * where they are. */ -PureResult pure_app_message_fields(PureApp *, uint32_t input, const char *key, int64_t base, PureSpan payload, - const uint32_t *placed); +PureResult pure_app_message_fields(PureApp *, uint32_t input, PureSpan payload, const uint32_t *placed); /* Visit the reply of the last message in order, without copying, until the * next event (deferred: copy it before a later save too). */ PureResult pure_app_reply(const PureApp *, bool (*visit)(void *, PureSpan), void *context); diff --git a/core/test-module.c b/core/test-module.c index 181f96d..2696e28 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -37,10 +37,10 @@ typedef struct { const char *name; double initial; } S; typedef struct { const char *name; uint32_t type; } F; /* kind 0 is a key input; otherwise PURE_KIND_MESSAGE with fields. Arguments * name their view by its number in key. */ -typedef struct { const char *name, *key; int kind; bool keyed; const F *fields; size_t nfields; } I; -#define KEY(name, key) {name, key, 0, false, NULL, 0} +typedef struct { const char *name, *key; int kind; const F *fields; size_t nfields; } I; +#define KEY(name, key) {name, key, 0, NULL, 0} /* A timer's period (milliseconds) rides in nfields. */ -#define TIMER(name, period) {name, NULL, PURE_KIND_TIMER, false, NULL, period} +#define TIMER(name, period) {name, NULL, PURE_KIND_TIMER, NULL, period} /* A number view, or a bool (i64) view when its length carries BOOL_VIEW (I64_VIEW). */ typedef struct { const char *name; uint32_t start, length; } V; #define BOOL_VIEW (1u << 31) @@ -97,7 +97,6 @@ static size_t build(unsigned char *out, Spec spec) { size_t names = 0; for (size_t i = 0; i < spec.ns; ++i) text[names++] = spec.s[i].name; for (size_t i = 0; i < spec.nt; ++i) text[names++] = spec.t[i].name; - for (size_t i = 0; i < spec.nc; ++i) text[names++] = spec.c[i]; for (size_t i = 0; i < spec.ni; ++i) { text[names++] = spec.in[i].name; if (!spec.in[i].kind) text[names++] = spec.in[i].key; @@ -124,17 +123,18 @@ static size_t build(unsigned char *out, Spec spec) { for (size_t i = 0; i < spec.ni; ++i) fields += spec.in[i].kind == PURE_KIND_TIMER ? 0 : spec.in[i].nfields; for (size_t i = 0; i < spec.nk; ++i) { memcpy(blob + blob_used, spec.k[i], strlen(spec.k[i])); blob_used += strlen(spec.k[i]); } unsigned char *p = out; - memset(p, 0, 40); - memcpy(p, "PUREMOD6", 8); + CHECK(!spec.nc); /* (collections are gone: 027's 4b) */ + memset(p, 0, 38); + memcpy(p, "PUREMOD7", 8); size_t states = spec.ns + spec.nt, views = spec.nv + spec.ntv + spec.nw; p[8] = (unsigned char)states; p[9] = (unsigned char)(states >> 8); p[10] = (unsigned char)spec.ni; p[12] = (unsigned char)views; p[14] = (unsigned char)spec.nh; put32(p + 16, (uint32_t)spec.no); put32(p + 20, (uint32_t)used); - p[24] = (unsigned char)fields; p[26] = (unsigned char)spec.nc; p[28] = (unsigned char)spec.nk; - p[30] = (unsigned char)spec.nr; p[36] = (unsigned char)record_fields; p[38] = (unsigned char)spec_nfn; - put32(p + 32, (uint32_t)blob_used); - p += 40; + p[24] = (unsigned char)fields; p[26] = (unsigned char)spec.nk; p[28] = (unsigned char)spec.nr; + p[34] = (unsigned char)record_fields; p[36] = (unsigned char)spec_nfn; + put32(p + 30, (uint32_t)blob_used); + p += 38; size_t n = 0; for (size_t i = 0; i < spec.ns; ++i) { p = put32(p, offset[n++]); p = put32(p, 0); p = put_double(p, spec.s[i].initial); } for (size_t i = 0; i < spec.nt; ++i) { @@ -150,13 +150,12 @@ static size_t build(unsigned char *out, Spec spec) { p = put32(p, spec.t[i].ref); p = put32(p, (spec.t[i].type & ~TR) >> 8); } - for (size_t i = 0; i < spec.nc; ++i) { p = put32(p, offset[n++]); p = put32(p, 1); p = put32(p, 1); } uint32_t field_offsets[64], field_start = 0; size_t nf = 0; for (size_t i = 0; i < spec.ni; ++i) { const I *in = &spec.in[i]; p = put32(p, offset[n++]); - *p++ = (unsigned char)(in->kind ? in->kind : 1); *p++ = in->keyed; *p++ = 0; *p++ = 0; + *p++ = (unsigned char)(in->kind ? in->kind : 1); *p++ = 0; *p++ = 0; *p++ = 0; bool timer = in->kind == PURE_KIND_TIMER; p = put32(p, timer ? (uint32_t)in->nfields : in->kind == PURE_KIND_ARGUMENTS ? (uint32_t)strtoul(in->key, NULL, 10) : in->kind ? 0 : offset[n++]); @@ -454,118 +453,7 @@ static void mutations(void) { printf("{\"module_mutations\":20000,\"accepted_after_damage\":%u}\n", accepted); } -static Spec editor_spec; - -/* An in-memory data host: every splice makes a new immutable version. */ -typedef struct { char key[65]; unsigned char *bytes; size_t size; int64_t revision; } Stored; -typedef struct { - Stored docs[8]; - size_t count, limit; - bool busy; - unsigned opened, closed; -} Memory; -typedef struct { - Memory *memory; - int index; /* -1 for an absent key */ - int64_t base; - unsigned char *versions[64]; - size_t sizes[64], count; -} Doc; - -static Stored *stored(Memory *m, const char *key) { - for (size_t i = 0; i < m->count; ++i) if (!strcmp(m->docs[i].key, key)) return &m->docs[i]; - return NULL; -} - -static PureResult fake_open(void *context, uint32_t collection, const char *key, void **out, int64_t *revision) { - Memory *m = context; - CHECK(collection == 0); - Doc *d = calloc(1, sizeof(*d)); - Stored *s = stored(m, key); - d->memory = m; - d->index = s ? (int)(s - m->docs) : -1; - if (!s) { - CHECK(m->count < 8); - d->index = (int)m->count; - snprintf(m->docs[m->count++].key, 65, "%s", key); - s = &m->docs[d->index]; - } - d->base = s->revision; - d->versions[0] = malloc(s->size + 1); - memcpy(d->versions[0], s->bytes ? s->bytes : (unsigned char *)"", s->size); - d->sizes[0] = s->size; - d->count = 1; - ++m->opened; - *out = d; - *revision = s->revision; - return PURE_OK; -} - -static PureVersion fake_version(void *context, void *document) { - (void)context; - Doc *d = document; - return (PureVersion){d->count - 1, d->sizes[d->count - 1]}; -} - -/* Two spans per read, so replies arrive in pieces. */ -static PureResult fake_read(void *context, void *document, PureVersion at, size_t offset, size_t length, - bool (*visit)(void *, PureSpan), void *visit_context) { - (void)context; - Doc *d = document; - CHECK(at.ref < d->count && at.bytes == d->sizes[at.ref] && offset + length <= at.bytes); - size_t half = length / 2; - if (half && !visit(visit_context, (PureSpan){d->versions[at.ref] + offset, half})) return PURE_OK; - if (length - half) visit(visit_context, (PureSpan){d->versions[at.ref] + offset + half, length - half}); - return PURE_OK; -} - -static PureResult fake_splice(void *context, void *document, size_t offset, size_t remove, PureSpan insert) { - Memory *m = context; - Doc *d = document; - size_t size = d->sizes[d->count - 1], next = size - remove + insert.size; - if (next > m->limit) return PURE_LIMIT; - CHECK(d->count < 64); - unsigned char *bytes = malloc(next + 1); - memcpy(bytes, d->versions[d->count - 1], offset); - if (insert.size) memcpy(bytes + offset, insert.data, insert.size); - memcpy(bytes + offset + insert.size, d->versions[d->count - 1] + offset + remove, size - offset - remove); - d->versions[d->count] = bytes; - d->sizes[d->count++] = next; - return PURE_OK; -} - -static PureResult fake_commit(void *context, void *document, int64_t *revision) { - Memory *m = context; - Doc *d = document; - Stored *s = &m->docs[d->index]; - *revision = s->revision; - if (m->busy) return PURE_BUSY; - if (s->revision != d->base) return PURE_CONFLICT; - free(s->bytes); - s->size = d->sizes[d->count - 1]; - s->bytes = malloc(s->size + 1); - memcpy(s->bytes, d->versions[d->count - 1], s->size); - *revision = ++s->revision; - /* Like a tree draft, a commit rebases: earlier versions are gone. */ - for (size_t i = 0; i + 1 < d->count; ++i) { free(d->versions[i]); d->versions[i] = NULL; d->sizes[i] = 0; } - d->versions[0] = d->versions[d->count - 1]; - d->sizes[0] = d->sizes[d->count - 1]; - d->count = 1; - d->base = s->revision; - return PURE_OK; -} - -static void fake_close(void *context, void *document) { - Memory *m = context; - Doc *d = document; - for (size_t i = 0; i < d->count; ++i) free(d->versions[i]); - free(d); - ++m->closed; -} - -static PureData fake_data(Memory *m) { - return (PureData){m, fake_open, fake_version, fake_read, fake_splice, fake_commit, fake_close, NULL, NULL, NULL}; -} +static Spec message_spec; typedef struct { unsigned char bytes[256]; size_t size, spans; } Collected; @@ -596,113 +484,69 @@ static PureSpan edit_payload(unsigned char *buffer, uint32_t start, uint32_t rem return (PureSpan){buffer, 8 + strlen(insert)}; } -enum { FIELD = PURE_OP_FIELD, DOC = PURE_OP_DOC, SIZE = PURE_OP_DOC_SIZE, LEN_ = PURE_OP_LEN, - REPL = PURE_OP_REPLACE, REPLY = PURE_OP_REPLY, REQ = PURE_OP_REQUIRE, LE = PURE_OP_LE }; +enum { FIELD = PURE_OP_FIELD, LEN_ = PURE_OP_LEN, REPLY = PURE_OP_REPLY, REQ = PURE_OP_REQUIRE, LE = PURE_OP_LE }; -/* The document editor as a module: show replies the document, edit splices - * start .. start + remove with insert, replies insert and counts edits. A - * keep message replies the document read before its own edit; guarded edits - * require the final size to stay within 8 bytes. */ +/* Messages: echo replies its insert; edit adds start + remove to sum, counts itself and replies its insert; guarded + * counts itself if its insert holds 8 bytes at most. A payload that does not hold the fields fails as limit, a failed + * require changes nothing, and a key input takes no message. */ static void messages(void) { static const F edit_fields[] = {{"start", 1}, {"remove", 1}, {"insert", 2}}; static const I inputs[] = { - {"show", NULL, PURE_KIND_MESSAGE, true, NULL, 0}, - {"edit", NULL, PURE_KIND_MESSAGE, true, edit_fields, 3}, - {"keep", NULL, PURE_KIND_MESSAGE, true, edit_fields + 2, 1}, - {"guarded", NULL, PURE_KIND_MESSAGE, true, edit_fields, 3}, + {"echo", NULL, PURE_KIND_MESSAGE, edit_fields + 2, 1}, + {"edit", NULL, PURE_KIND_MESSAGE, edit_fields, 3}, + {"guarded", NULL, PURE_KIND_MESSAGE, edit_fields, 3}, KEY("tick", "Space"), }; - static const S states[] = {{"edits", 0}}; - static const char *const collections[] = {"documents"}; + static const S states[] = {{"edits", 0}, {"sum", 0}}; static const O code[] = { - {DOC, 0, 0}, {REPLY, 0, 0}, /* 0 show */ - {FIELD, 0, 0}, {FIELD, 0, 0}, {FIELD, 1, 0}, {ADD, 0, 0}, {FIELD, 2, 0}, /* 2 edit */ - {REPL, 0, 0}, {FIELD, 2, 0}, {REPLY, 0, 0}, {L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, - {DOC, 0, 0}, {REPLY, 0, 0}, {C, 0, 0}, {C, 0, 0}, {FIELD, 0, 0}, {REPL, 0, 0}, /* 14 keep: prepend */ - {FIELD, 0, 0}, {FIELD, 0, 0}, {FIELD, 1, 0}, {ADD, 0, 0}, {FIELD, 2, 0}, /* 20 guarded */ - {REPL, 0, 0}, {L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, - {SIZE, 0, 0}, {C, 0, 8}, {LE, 0, 0}, {REQ, 0, 0}, + {FIELD, 0, 0}, {REPLY, 0, 0}, /* 0 echo */ + {FIELD, 0, 0}, {FIELD, 1, 0}, {ADD, 0, 0}, {L, 1, 0}, {ADD, 0, 0}, {ST, 1, 0}, /* 2 edit */ + {FIELD, 2, 0}, {REPLY, 0, 0}, {L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, + {L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, /* 14 guarded */ + {FIELD, 2, 0}, {LEN_, 0, 0}, {C, 0, 8}, {LE, 0, 0}, {REQ, 0, 0}, }; - static const H handlers[] = {{1, 0, 2}, {3, 2, 12}, {5, 14, 6}, {7, 20, 14}}; + static const H handlers[] = {{1, 0, 2}, {3, 2, 12}, {5, 14, 9}}; static const V views[] = {{"count", 0, 0}}; - Spec spec = {states, 1, inputs, 5, views, 0, handlers, 4, code, LEN(code), collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; - editor_spec = spec; + Spec spec = {states, 2, inputs, 4, views, 0, handlers, 3, code, LEN(code), NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; + message_spec = spec; PureResult loaded = load(spec, &module_a); if (loaded != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); } - Memory memory = {.limit = 100}; PureApp app; - CHECK(pure_app_start(&app, &module_a, fake_data(&memory)) == PURE_OK); + CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK); unsigned char buffer[64]; - - CHECK(pure_app_message(&app, 0, "doc", -1, (PureSpan){0}) == PURE_NOT_FOUND && app.revision == 0); - CHECK(pure_app_message(&app, 1, "doc", 0, edit_payload(buffer, 0, 0, "hello")) == PURE_OK); - CHECK(app.revision == 1 && replied(&app, "hello") && app.values[0].num == 1); - CHECK(pure_app_message(&app, 0, "doc", -1, (PureSpan){0}) == PURE_OK && app.revision == 1 && replied(&app, "hello")); - /* stale base conflicts before bounds; bounds fail as limit; nothing changes */ - CHECK(pure_app_message(&app, 1, "doc", 0, edit_payload(buffer, 99, 0, "x")) == PURE_CONFLICT && app.revision == 1); - CHECK(pure_app_message(&app, 1, "doc", 1, edit_payload(buffer, 99, 0, "x")) == PURE_LIMIT && app.revision == 1); - CHECK(pure_app_message(&app, 1, "doc", 1, edit_payload(buffer, 2, 9, "x")) == PURE_LIMIT); - CHECK(pure_app_message(&app, 1, "doc", 1, (PureSpan){buffer, 7}) == PURE_LIMIT && app.revision == 0); - CHECK(pure_app_message(&app, 1, "doc", -1, edit_payload(buffer, 0, 0, "x")) == PURE_INVALID); - CHECK(pure_app_message(&app, 1, "bad/key", 1, edit_payload(buffer, 0, 0, "x")) == PURE_INVALID); - CHECK(pure_app_message(&app, 4, "doc", 1, (PureSpan){0}) == PURE_INVALID); - CHECK(memory.docs[0].revision == 1 && memory.docs[0].size == 5 && app.values[0].num == 1); - /* A busy commit changes neither the document nor the states. */ - memory.busy = true; - CHECK(pure_app_message(&app, 1, "doc", 1, edit_payload(buffer, 5, 0, "!")) == PURE_BUSY); - memory.busy = false; - CHECK(memory.docs[0].size == 5 && app.values[0].num == 1 && app.events == 2); - /* The size limit of the data host fails as limit. */ - memory.limit = 6; - CHECK(pure_app_message(&app, 1, "doc", 1, edit_payload(buffer, 5, 0, "!!")) == PURE_LIMIT); - memory.limit = 100; - /* A reply read before the event's own edit keeps its content after commit. */ - CHECK(pure_app_message(&app, 2, "doc", 1, (PureSpan){(const unsigned char *)">> ", 3}) == PURE_OK); - CHECK(app.revision == 2 && replied(&app, "hello")); - CHECK(pure_app_message(&app, 0, "doc", -1, (PureSpan){0}) == PURE_OK && replied(&app, ">> hello")); - /* require: an edit that would exceed 8 bytes changes nothing */ - CHECK(pure_app_message(&app, 3, "doc", 2, edit_payload(buffer, 8, 0, "!")) == PURE_LIMIT); - CHECK(memory.docs[0].size == 8 && app.values[0].num == 1); - CHECK(pure_app_message(&app, 3, "doc", 2, edit_payload(buffer, 0, 3, "")) == PURE_OK && app.revision == 3); - CHECK(app.values[0].num == 2); - /* Another key is its own document. */ - CHECK(pure_app_message(&app, 1, "other", 0, edit_payload(buffer, 0, 0, "abc")) == PURE_OK && app.revision == 1); + CHECK(pure_app_message(&app, 0, (PureSpan){(const unsigned char *)"hello", 5}) == PURE_OK && replied(&app, "hello")); + CHECK(pure_app_message(&app, 1, edit_payload(buffer, 2, 3, "xy")) == PURE_OK && replied(&app, "xy")); + CHECK(app.values[0].num == 1 && app.values[1].num == 5); + CHECK(pure_app_message(&app, 1, (PureSpan){buffer, 7}) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); + CHECK(pure_app_message(&app, 2, edit_payload(buffer, 0, 0, "123456789")) == PURE_LIMIT); + CHECK(app.values[0].num == 1 && app.values[1].num == 5); + CHECK(pure_app_message(&app, 2, edit_payload(buffer, 0, 0, "12345678")) == PURE_OK && app.values[0].num == 2); + CHECK(pure_app_message(&app, 3, (PureSpan){0}) == PURE_INVALID && app.values[0].num == 2 && app.events == 3); pure_app_stop(&app); - CHECK(memory.opened == memory.closed); - for (size_t i = 0; i < memory.count; ++i) free(memory.docs[i].bytes); /* Verifier rules for typed operations. */ static const O field_in_key[] = {{FIELD, 0, 0}, {ST, 0, 0}}; - static const H key_handler[] = {{9, 0, 2}}; + static const H key_handler[] = {{7, 0, 2}}; Spec bad = spec; bad.o = field_in_key; bad.no = 2; bad.h = key_handler; bad.nh = 1; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "message operation outside")); - static const O num_insert[] = {{C, 0, 0}, {C, 0, 0}, {C, 0, 1}, {REPL, 0, 0}}; - static const H edit_handler[] = {{3, 0, 4}}; - bad.o = num_insert; bad.no = 4; bad.h = edit_handler; - CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand type")); static const O bytes_state[] = {{FIELD, 2, 0}, {ST, 0, 0}}; - static const H edit_handler2[] = {{3, 0, 2}}; - bad.o = bytes_state; bad.no = 2; bad.h = edit_handler2; + static const H edit_handler[] = {{3, 0, 2}}; + bad.o = bytes_state; bad.no = 2; bad.h = edit_handler; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand type")); static const O far_field[] = {{FIELD, 3, 0}, {REPLY, 0, 0}}; - bad.o = far_field; bad.h = edit_handler2; + bad.o = far_field; bad.h = edit_handler; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "no such state, field")); - static const I unkeyed[] = {{"note", NULL, PURE_KIND_MESSAGE, false, NULL, 0}}; - static const O doc_read[] = {{DOC, 0, 0}, {REPLY, 0, 0}}; - static const H note_handler[] = {{1, 0, 2}}; - bad = (Spec){states, 1, unkeyed, 1, views, 0, note_handler, 1, doc_read, 2, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; - CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "document operation outside")); static const F unknown_type[] = {{"insert", 8}, {"start", 1}}; - static const I misplaced[] = {{"edit", NULL, PURE_KIND_MESSAGE, true, unknown_type, 2}}; - bad = (Spec){states, 1, misplaced, 1, views, 0, NULL, 0, NULL, 0, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; + static const I misplaced[] = {{"edit", NULL, PURE_KIND_MESSAGE, unknown_type, 2}}; + bad = (Spec){states, 1, misplaced, 1, views, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "message field")); static const H even[] = {{2, 0, 0}}; - bad = (Spec){states, 1, inputs, 1, views, 0, even, 1, NULL, 0, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; + bad = (Spec){states, 1, inputs, 1, views, 0, even, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "handler event")); static const V field_view[] = {{"v", 0, 1}}; static const O field_only[] = {{FIELD, 0, 0}}; - bad = (Spec){states, 1, inputs, 2, field_view, 1, NULL, 0, field_only, 1, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; + bad = (Spec){states, 1, inputs, 2, field_view, 1, NULL, 0, field_only, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}; CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view with an effect")); /* A value may cross a join when every path brings the same stack. */ static const O branch_value[] = {{C, 0, 1}, {C, 1, 1}, {JZ, 3, 0}, {ST, 0, 0}}; @@ -718,7 +562,7 @@ static void messages(void) { /* Damaged message modules either fail to load or run messages safely. */ static void message_mutations(void) { static unsigned char valid[65536], bytes[65536]; - size_t size = build(valid, editor_spec); + size_t size = build(valid, message_spec); uint32_t state = 0x9e3779b9u; unsigned accepted = 0; for (unsigned round = 0; round < 20000; ++round) { @@ -728,19 +572,17 @@ static void message_mutations(void) { bytes[next_random(&state) % size] ^= (unsigned char)(1u << (next_random(&state) % 8)); if (pure_module_load(bytes, size, &module_b, error, sizeof(error)) != PURE_OK) continue; ++accepted; - Memory memory = {.limit = 64}; PureApp app; - CHECK(pure_app_start(&app, &module_b, fake_data(&memory)) == PURE_OK); + CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK); unsigned char payload[32]; for (unsigned e = 0; e < 12; ++e) { for (size_t k = 0; k < sizeof(payload); ++k) payload[k] = (unsigned char)next_random(&state); put32(payload, next_random(&state) % 12); put32(payload + 4, next_random(&state) % 4); PureSpan span = {payload, next_random(&state) % sizeof(payload)}; - uint32_t input = next_random(&state) % 5; + uint32_t input = next_random(&state) % module_b.inputs; if (module_b.input[input].kind == PURE_KIND_MESSAGE) { - int64_t base = (int64_t)(next_random(&state) % 4) - 1; - if (pure_app_message(&app, input, "doc", base, span) == PURE_OK) { + if (pure_app_message(&app, input, span) == PURE_OK) { size_t reply = 0; CHECK(pure_app_reply(&app, count_bytes, &reply) == PURE_OK && reply <= 256); } @@ -749,8 +591,6 @@ static void message_mutations(void) { for (uint32_t s = 0; s < module_b.states; ++s) CHECK(isfinite(app.values[s].num)); } pure_app_stop(&app); - CHECK(memory.opened == memory.closed); - for (size_t i = 0; i < memory.count; ++i) free(memory.docs[i].bytes); } printf("{\"message_module_mutations\":20000,\"accepted_after_damage\":%u}\n", accepted); } @@ -814,7 +654,7 @@ static void texts(void) { "\xeb\xa9\x94\xeb\xaa\xa8", "XYZ", " \xc2\xb7 ", "\xec\x9e\x90"}; static const I inputs[] = { - {"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("left", "ArrowLeft"), KEY("right", "ArrowRight"), + {"typed", NULL, PURE_KIND_TEXT, NULL, 0}, KEY("left", "ArrowLeft"), KEY("right", "ArrowRight"), KEY("up", "ArrowUp"), KEY("down", "ArrowDown"), KEY("back", "Backspace"), KEY("home", "Home"), KEY("bad", "KeyB"), }; static const O code[] = { @@ -2126,7 +1966,7 @@ static void lists(void) { static const R records[] = {{"task", task_fields, 2}}; static const char *const constants[] = {"", "new", "x"}; static const I inputs[] = { - {"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("ins", "KeyI"), KEY("del", "Delete"), + {"typed", NULL, PURE_KIND_TEXT, NULL, 0}, KEY("ins", "KeyI"), KEY("del", "Delete"), KEY("up", "ArrowUp"), KEY("down", "ArrowDown"), KEY("toggle", "Space"), KEY("rename", "KeyR"), KEY("bad", "KeyB"), }; static const O code[] = { @@ -2334,7 +2174,7 @@ static void tables(void) { static const RF task_fields[] = {{"title", 1, 0}, {"done", 2, 0}}; static const R records[] = {{"task", task_fields, 2}}; static const char *const constants[] = {"", "x", " ", "empty"}; - static const I inputs[] = {{"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("toggle", "Space")}; + static const I inputs[] = {{"typed", NULL, PURE_KIND_TEXT, NULL, 0}, KEY("toggle", "Space")}; static const O code[] = { {TI, 0, 0}, {C, 1, 0}, {LA, 1, 0}, {XE, 1, 0}, {XS, 2, 0}, /* 0 typed */ {XL, 2, 0}, {XL, 2, 0}, {IG, DONE, 0}, {NOT, 0, 0}, {IS, DONE, 0}, /* 5 toggle */ @@ -2745,32 +2585,19 @@ static void stored_frames(void) { remove_store(path); } -static bool list_keys(void *context, const char *key) { - strcat(context, key); - strcat(context, ","); - return true; -} - -/* A message's document and the states its handlers change commit together. */ +/* A message's changes are stored with its event, and one that fails stores nothing. */ static void stored_messages(void) { char *path = new_store(); - CHECK(load(editor_spec, &module_a) == PURE_OK); + CHECK(load(message_spec, &module_a) == PURE_OK); PureApp app; unsigned char buffer[64]; CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK); - CHECK(pure_app_message(&app, 1, "doc", 0, edit_payload(buffer, 0, 0, "hello")) == PURE_OK && app.revision == 1); - CHECK(app.values[0].num == 1); - CHECK(pure_app_message(&app, 1, "doc", 0, edit_payload(buffer, 0, 0, "x")) == PURE_CONFLICT && app.values[0].num == 1); - /* An edit past the size guard fails: neither the document nor the count moves. */ - CHECK(pure_app_message(&app, 3, "doc", 1, edit_payload(buffer, 5, 0, "-world")) == PURE_LIMIT && app.values[0].num == 1); - CHECK(pure_app_message(&app, 3, "doc", 1, edit_payload(buffer, 5, 0, "!")) == PURE_OK && app.values[0].num == 2); + CHECK(pure_app_message(&app, 1, edit_payload(buffer, 2, 3, "hello")) == PURE_OK && replied(&app, "hello")); + CHECK(pure_app_message(&app, 2, edit_payload(buffer, 0, 0, "-world, long")) == PURE_LIMIT && app.values[0].num == 1); + CHECK(pure_app_message(&app, 2, edit_payload(buffer, 0, 0, "!")) == PURE_OK && app.values[0].num == 2); pure_app_stop(&app); - CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0].num == 2); - CHECK(pure_app_message(&app, 0, "doc", -1, (PureSpan){0}) == PURE_OK && app.revision == 2 && replied(&app, "hello!")); + CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0].num == 2 && app.values[1].num == 5); pure_app_stop(&app); - /* Documents stay documents: the app's states are not among the keys. */ - char seen[256] = ""; - CHECK(pure_store_keys(path, list_keys, seen) == PURE_OK && !strcmp(seen, "doc,")); remove_store(path); } @@ -3051,82 +2878,12 @@ static void deferred_bursts(void) { remove_store(path); } -/* Messages in a burst: documents of different keys commit together with the - * states, each moving one revision; a second message for a key saves the - * burst first, and so does a full burst. */ -static void deferred_messages(void) { - char *path = new_store(); - CHECK(load(editor_spec, &module_a) == PURE_OK); - PureApp app; - unsigned char buffer[64]; - char key[16]; - CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && pure_app_defer(&app, true)); - for (int i = 0; i < 3; ++i) { - snprintf(key, sizeof(key), "k%d", i); - CHECK(pure_app_message(&app, 1, key, 0, edit_payload(buffer, 0, 0, "ab")) == PURE_OK && app.revision == 1); - } - CHECK(app.values[0].num == 3 && app.pending_count == 3 && app.open); - /* The same key again: the burst is saved first, then this edit joins a new one. */ - CHECK(pure_app_message(&app, 1, "k0", 1, edit_payload(buffer, 2, 0, "c")) == PURE_OK && app.revision == 2); - CHECK(app.pending_count == 1 && app.values[0].num == 4); - CHECK(pure_app_message(&app, 1, "k1", 0, edit_payload(buffer, 0, 0, "x")) == PURE_CONFLICT); - CHECK(pure_app_save(&app) == PURE_OK && !app.open && !app.pending_count); - for (int i = 0; i < PURE_APP_PENDING + 1; ++i) { - snprintf(key, sizeof(key), "m%d", i); - CHECK(pure_app_message(&app, 1, key, 0, edit_payload(buffer, 0, 0, "y")) == PURE_OK); - CHECK(app.pending_count == (i < PURE_APP_PENDING ? (unsigned)i + 1 : 1)); /* full: saved on the way */ - } - pure_app_stop(&app); /* the last edit was never saved */ - CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0].num == 4 + PURE_APP_PENDING); - CHECK(pure_app_message(&app, 0, "k0", -1, (PureSpan){0}) == PURE_OK && app.revision == 2 && replied(&app, "abc")); - snprintf(key, sizeof(key), "m%d", PURE_APP_PENDING); - CHECK(pure_app_message(&app, 0, key, -1, (PureSpan){0}) == PURE_NOT_FOUND); - snprintf(key, sizeof(key), "m%d", PURE_APP_PENDING - 1); - CHECK(pure_app_message(&app, 0, key, -1, (PureSpan){0}) == PURE_OK && replied(&app, "y")); - pure_app_stop(&app); - remove_store(path); -} - -/* The burst's keys are found in one look (an index over their hashes, most - * slots shared at 200 keys): every pending key and no other, reads as well - * as edits, and a message for one of them saves the burst first. */ -static void pending_keys(void) { - char *path = new_store(); - CHECK(load(editor_spec, &module_a) == PURE_OK); - PureApp app; - unsigned char buffer[64]; - char key[16]; - CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && pure_app_defer(&app, true)); - for (int i = 0; i < 200; ++i) { - snprintf(key, sizeof(key), "p%d", i); - CHECK(!pure_app_pending(&app, key)); - CHECK(pure_app_message(&app, 1, key, 0, edit_payload(buffer, 0, 0, "q")) == PURE_OK); - } - CHECK(pure_app_save(&app) == PURE_OK && !pure_app_pending(&app, "p0")); - for (int i = 0; i < 200; ++i) { - snprintf(key, sizeof(key), "p%d", i); - CHECK((i % 2 ? pure_app_message(&app, 0, key, -1, (PureSpan){0}) - : pure_app_message(&app, 1, key, 1, edit_payload(buffer, 1, 0, "r"))) == PURE_OK); - } - for (int i = 0; i < 210; ++i) { - snprintf(key, sizeof(key), "p%d", i); - CHECK(pure_app_pending(&app, key) == (i < 200)); - } - CHECK(!pure_app_pending(&app, "p") && !pure_app_pending(&app, "p1000") && app.pending_count == 200); - CHECK(pure_app_message(&app, 0, "p150", -1, (PureSpan){0}) == PURE_OK && app.pending_count == 1); - CHECK(pure_app_pending(&app, "p150") && !pure_app_pending(&app, "p151")); - pure_app_stop(&app); - remove_store(path); -} - static void deferred(void) { static const char *const list_words[] = {"a", "\xea\xb0\x80", "", "\n", "\xf0\x9f\x98\x80"}; static const char *const text_words[] = {"a", "\xea\xb0\x80", "\n", "\xf0\x9f\x98\x80", "xy\nz", ""}; deferred_equivalence(list_spec, list_words, LEN(list_words), 0x2545f491u); deferred_equivalence(text_spec, text_words, LEN(text_words), 0x1b873593u); deferred_bursts(); - deferred_messages(); - pending_keys(); } /* Text inserted at a cursor goes before it; at a mark (a cursor that stays), @@ -3135,7 +2892,7 @@ static void marks(void) { enum { MARK = 2 | PURE_CURSOR_STAYS << 8 }; static const T textual[] = {{"note", 1, 0}, {"at", 2, 0}, {"mark", MARK, 0}}; static const char *const constants[] = {"ab"}; - static const I inputs[] = {{"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("keep", "KeyK"), KEY("back", "Backspace")}; + static const I inputs[] = {{"typed", NULL, PURE_KIND_TEXT, NULL, 0}, KEY("keep", "KeyK"), KEY("back", "Backspace")}; static const O code[] = { {CUL, 1, 0}, {TI, 0, 0}, {TINS, 0, 0}, /* 0 typed: at the cursor */ {CUL, 1, 0}, {CS, 2, 0}, /* 3 keep: mark = at */ @@ -3174,7 +2931,7 @@ static const RF fold_fields[] = {{"title", 1, 0}, {"done", 2, 0}, {"points", 0, static const R fold_records[] = {{"task", fold_fields, 3}}; static const T fold_states[] = {{"tasks", 3, 0}, {"at", 4, 0}}; static const char *const fold_constants[] = {""}; -static const I fold_inputs[] = {{"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("toggle", "KeyT")}; +static const I fold_inputs[] = {{"typed", NULL, PURE_KIND_TEXT, NULL, 0}, KEY("toggle", "KeyT")}; enum { F_TITLE = 0, F_DONE = 1u << 16, F_POINTS = 2u << 16 }; static const O fold_code[] = { {TI, 0, 0}, {C, 1, 0}, {TI, 0, 0}, {CHR, 0, 0}, {LA, 0, 0}, {XE, 0, 0}, {XS, 1, 0}, /* 0 typed */ @@ -3656,8 +3413,8 @@ static const RF stock_fields[] = {{"item", 0, 0}, {"left", 0, 0}}, line_fields[] entry_fields[] = {{"item", 0, 0}, {"qty", 0, 0}}; static const R walk_records[] = {{"Stock", stock_fields, 2}, {"Line", line_fields, 2}, {"Entry", entry_fields, 2}}; static const F order_fields[] = {{"lines", PURE_FIELD_ROWS | W_LINES << 8}}, restock_fields[] = {{"item", 1}, {"left", 1}}; -static const I walk_inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, false, order_fields, 1}, KEY("clean", "KeyC"), - {"restock", NULL, PURE_KIND_MESSAGE, false, restock_fields, 2}}; +static const I walk_inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, order_fields, 1}, KEY("clean", "KeyC"), + {"restock", NULL, PURE_KIND_MESSAGE, restock_fields, 2}}; static const O walk_code[] = { /* 0 order: for l in order.lines { stock[l.item].left -= l.qty (not below 0); log.append(l) } */ {EACH, WALKING(W_LINES, 20), 0}, @@ -3730,7 +3487,7 @@ static void walks(void) { unsigned char payload[8]; put32(payload, (uint32_t)item); put32(payload + 4, (uint32_t)left); - result = pure_app_message(&app, 2, NULL, -1, (PureSpan){payload, 8}); + result = pure_app_message(&app, 2, (PureSpan){payload, 8}); size_t at = 0; while (at < shop.stocked && shop.stock[at].item < item) ++at; if (at < shop.stocked && shop.stock[at].item == item) ok = false; @@ -3756,7 +3513,7 @@ static void walks(void) { } for (size_t k = 0; !ok && k < lines; ++k) /* the rest of the rows, for the payload */ if (size < (k + 1) * 16) size += put_row(payload + size, (double)(next_random(&seed) % 13), 1); - result = pure_app_message(&app, 0, NULL, -1, (PureSpan){size ? payload : NULL, size}); + result = pure_app_message(&app, 0, (PureSpan){size ? payload : NULL, size}); if (ok) { shop.n += 1; shop.sum += qty_total; @@ -3804,13 +3561,13 @@ static void walks(void) { unsigned char short_row[12] = {0}, not_finite[16]; put_double(not_finite, 1); put_double(not_finite + 8, NAN); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){short_row, 12}) == PURE_LIMIT && same_shop(&app, &before)); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){not_finite, 16}) == PURE_LIMIT && same_shop(&app, &before)); + CHECK(pure_app_message(&app, 0, (PureSpan){short_row, 12}) == PURE_LIMIT && same_shop(&app, &before)); + CHECK(pure_app_message(&app, 0, (PureSpan){not_finite, 16}) == PURE_LIMIT && same_shop(&app, &before)); pure_app_stop(&app); /* What a walk and a rows field may not be. */ static const F note_fields[] = {{"lines", PURE_FIELD_ROWS | W_LINES << 8}, {"text", 4}}; - static const I note_inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, false, note_fields, 2}}; + static const I note_inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, note_fields, 2}}; static const O into_walked[] = {{EACH, WALKING(W_STOCK, 3), 0}, {C, 0, 1}, {C, 0, 2}, {LA, W_STOCK, 0}}; static const O nested[] = {{EACH, WALKING(W_STOCK, 4), 0}, {EACH, WALKING(W_LOG, 3), 0}, {C, 0, 1}, {C, 0, 2}, {LA, W_STOCK, 0}}; @@ -3824,8 +3581,8 @@ static void walks(void) { static const V walk_view[] = {{"v", 0, 4}}; static const T unnamed[] = {{"stock", 3 | 1 << 8, 0}, {"lines", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}, {"log", 3, 2}}; static const F logged_rows[] = {{"lines", PURE_FIELD_ROWS | W_LOG << 8}}; - static const I logged_inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, false, logged_rows, 1}}; - static const I sent_rows[] = {{"order", NULL, PURE_KIND_OUTPUT, false, order_fields, 1}}; + static const I logged_inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, logged_rows, 1}}; + static const I sent_rows[] = {{"order", NULL, PURE_KIND_OUTPUT, order_fields, 1}}; const struct { Spec spec; const char *reason; } refused[] = { {walk_spec(into_walked, LEN(into_walked), note_inputs, 1, first3, 1, walk_lists, NULL, 0), "insert into the list walked"}, {walk_spec(nested, LEN(nested), note_inputs, 1, first5, 1, walk_lists, NULL, 0), "insert into the list walked"}, @@ -3871,7 +3628,7 @@ static void walk_mutations(void) { for (size_t k = 0; k + 16 <= length && next_random(&state) % 2; k += 16) put_row(payload + k, (double)(next_random(&state) % 8), (double)(next_random(&state) % 3)); if (module_b.input[input].kind == PURE_KIND_MESSAGE) - pure_app_message(&app, input, NULL, -1, (PureSpan){payload, length}); + pure_app_message(&app, input, (PureSpan){payload, length}); else if (module_b.input[input].kind == PURE_KIND_KEY) pure_app_input(&app, input, PURE_EDGE_DOWN); for (uint32_t s_ = 0; s_ < module_b.states; ++s_) @@ -3897,11 +3654,11 @@ static const R argued_records[] = {{"Account", account_fields, 3}}; static const F pay_fields[] = {{"b", 1}}, add_fields[] = {{"aid", 1}, {"bid", 1}, {"balance", 3}}, aid_fields[] = {{"aid", 1}}, min_fields[] = {{"min", 3}}, name_fields[] = {{"name", 4}}, b_fields[] = {{"b", 1}}; static const I argued_inputs[] = { - {"pay", NULL, PURE_KIND_MESSAGE, false, pay_fields, 1}, {"add", NULL, PURE_KIND_MESSAGE, false, add_fields, 3}, - {"balance.args", "0", PURE_KIND_ARGUMENTS, false, aid_fields, 1}, - {"rich.args", "1", PURE_KIND_ARGUMENTS, false, min_fields, 1}, - {"echo.args", "2", PURE_KIND_ARGUMENTS, false, name_fields, 1}, - {"branch.args", "3", PURE_KIND_ARGUMENTS, false, b_fields, 1}}; + {"pay", NULL, PURE_KIND_MESSAGE, pay_fields, 1}, {"add", NULL, PURE_KIND_MESSAGE, add_fields, 3}, + {"balance.args", "0", PURE_KIND_ARGUMENTS, aid_fields, 1}, + {"rich.args", "1", PURE_KIND_ARGUMENTS, min_fields, 1}, + {"echo.args", "2", PURE_KIND_ARGUMENTS, name_fields, 1}, + {"branch.args", "3", PURE_KIND_ARGUMENTS, b_fields, 1}}; static const O argued_code[] = { /* 0 balance(aid) = accounts[aid].balance */ {FIELD, 0, 0}, {PURE_OP_LIST_FIND, A_ACCOUNTS, 0}, {IG, ITEM(A_ACCOUNTS, 2), 0}, @@ -3949,7 +3706,7 @@ static void view_arguments(void) { put32(payload, aid); put32(payload + 4, aid % 3); put_double(payload + 8, aid * 10.0); - CHECK(pure_app_message(&app, 1, NULL, -1, (PureSpan){payload, 16}) == PURE_OK); + CHECK(pure_app_message(&app, 1, (PureSpan){payload, 16}) == PURE_OK); } double value; unsigned char four[8]; @@ -3985,17 +3742,17 @@ static void view_arguments(void) { } } put32(four, 2); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){four, 4}) == PURE_OK && app.values[A_N].num == 10); + CHECK(pure_app_message(&app, 0, (PureSpan){four, 4}) == PURE_OK && app.values[A_N].num == 10); pure_app_stop(&app); /* What arguments may not be. */ - static const I on_column[] = {{"pay", NULL, PURE_KIND_MESSAGE, false, pay_fields, 1}, - {"aid.args", "4", PURE_KIND_ARGUMENTS, false, aid_fields, 1}}; - static const I misnamed[] = {{"pay", NULL, PURE_KIND_MESSAGE, false, pay_fields, 1}, - {"balance.argz", "0", PURE_KIND_ARGUMENTS, false, aid_fields, 1}}; - static const I twice[] = {{"pay", NULL, PURE_KIND_MESSAGE, false, pay_fields, 1}, - {"balance.args", "0", PURE_KIND_ARGUMENTS, false, aid_fields, 1}, - {"balance.args", "0", PURE_KIND_ARGUMENTS, false, aid_fields, 1}}; + static const I on_column[] = {{"pay", NULL, PURE_KIND_MESSAGE, pay_fields, 1}, + {"aid.args", "4", PURE_KIND_ARGUMENTS, aid_fields, 1}}; + static const I misnamed[] = {{"pay", NULL, PURE_KIND_MESSAGE, pay_fields, 1}, + {"balance.argz", "0", PURE_KIND_ARGUMENTS, aid_fields, 1}}; + static const I twice[] = {{"pay", NULL, PURE_KIND_MESSAGE, pay_fields, 1}, + {"balance.args", "0", PURE_KIND_ARGUMENTS, aid_fields, 1}, + {"balance.args", "0", PURE_KIND_ARGUMENTS, aid_fields, 1}}; static const H handled[] = {{5, 31, 4}}; const struct { Spec spec; const char *reason; } refused[] = { {argued_spec(on_column, 2, NULL, 0), "arguments"}, @@ -4024,7 +3781,7 @@ static const T local_lists[] = {{"xs", 3, 0}}; static const RF local_fields[] = {{"a", 0, 0}}; static const R local_records[] = {{"X", local_fields, 1}}; static const F local_add_fields[] = {{"a", 3}}; -static const I local_inputs[] = {KEY("sum", "KeyS"), {"add", NULL, PURE_KIND_MESSAGE, false, local_add_fields, 1}}; +static const I local_inputs[] = {KEY("sum", "KeyS"), {"add", NULL, PURE_KIND_MESSAGE, local_add_fields, 1}}; static const O local_code[] = { /* 0 on sum: total = total + x.a for each x (total starts at 0); n = count(x in xs where x.a < total); * last = xs.last; m = total + last.a (every event: total is 0 again) */ @@ -4056,7 +3813,7 @@ static void locals(void) { unsigned char payload[8]; items[count] = (double)(next_random(&seed) % 20) - 5; put_double(payload, items[count++]); - CHECK(pure_app_message(&app, 1, NULL, -1, (PureSpan){payload, 8}) == PURE_OK); + CHECK(pure_app_message(&app, 1, (PureSpan){payload, 8}) == PURE_OK); CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK); double total = 1, below = 0; for (size_t i = 0; i < count; ++i) total += items[i]; @@ -4103,7 +3860,7 @@ static const T range_lists[] = {{"l", 3 | 1 << 8, 0}}; static const RF range_fields[] = {{"key", 0, 0}, {"a", 0, 0}, {"b", 0, 0}}; static const R range_records[] = {{"K", range_fields, 3}}; static const F bounds_fields[] = {{"lo", 3}, {"hi", 3}}; -static const I range_inputs[] = {{"bounds", NULL, PURE_KIND_MESSAGE, false, bounds_fields, 2}, KEY("walk", "KeyW"), +static const I range_inputs[] = {{"bounds", NULL, PURE_KIND_MESSAGE, bounds_fields, 2}, KEY("walk", "KeyW"), KEY("drift", "KeyD"), KEY("slice", "KeyS")}; #define KEYED(field) {IG, ITEM(R_L, field), 0} static const O range_code[] = { @@ -4190,8 +3947,8 @@ static void ranges(void) { double lo = (double)(next_random(&seed) % 900) - 50, hi = lo + (double)(next_random(&seed) % 400) - 20; put_double(payload, lo); put_double(payload + 8, hi); - CHECK(pure_app_message(&a, 0, NULL, -1, (PureSpan){payload, 16}) == PURE_OK && - pure_app_message(&b, 0, NULL, -1, (PureSpan){payload, 16}) == PURE_OK); + CHECK(pure_app_message(&a, 0, (PureSpan){payload, 16}) == PURE_OK && + pure_app_message(&b, 0, (PureSpan){payload, 16}) == PURE_OK); /* The slice: its items alone, a division by zero among them failing it. */ size_t sliced = 0; bool divided = true; @@ -4312,9 +4069,9 @@ static const RF score_fields[] = {{"score", 0, 0}}, flag_fields[] = {{"on", 2, 0 static const R keyed_records[] = {{"person", person_fields, 2}, {"score", score_fields, 1}, {"flag", flag_fields, 1}}; static const char *const keyed_constants[] = {"", "bob", "m"}; static const I keyed_inputs[] = { - {"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, {"seek", NULL, PURE_KIND_TEXT, false, NULL, 0}, - {"has", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("drop", "Delete"), KEY("bump", "KeyB"), - KEY("two", "KeyN"), KEY("zero", "KeyO"), KEY("minus_zero", "KeyM"), {"twice", NULL, PURE_KIND_TEXT, false, NULL, 0}, + {"typed", NULL, PURE_KIND_TEXT, NULL, 0}, {"seek", NULL, PURE_KIND_TEXT, NULL, 0}, + {"has", NULL, PURE_KIND_TEXT, NULL, 0}, KEY("drop", "Delete"), KEY("bump", "KeyB"), + KEY("two", "KeyN"), KEY("zero", "KeyO"), KEY("minus_zero", "KeyM"), {"twice", NULL, PURE_KIND_TEXT, NULL, 0}, }; static const O keyed_code[] = { {TI, 0, 0}, {C, 0, 0}, {LA, PEOPLE, 0}, {TI, 0, 0}, {FIND, PEOPLE, 0}, {XS, K_AT, 0}, /* 0 typed: add, point at it */ @@ -4799,7 +4556,7 @@ static void message_fields(void) { static const char *const constants[] = {""}; static const F fields[] = {{"title", PURE_FIELD_TEXT}, {"points", PURE_FIELD_NUMBER}, {"note", PURE_FIELD_BYTES}, {"count", PURE_FIELD_U32}}; - static const I inputs[] = {{"post", NULL, PURE_KIND_MESSAGE, false, fields, 4}}; + static const I inputs[] = {{"post", NULL, PURE_KIND_MESSAGE, fields, 4}}; static const O code[] = {{FIELD, 1, 0}, {FIELD, 3, 0}, {ADD, 0, 0}, {ST, 0, 0}, {FIELD, 0, 0}, {TSET, 1, 0}, {FIELD, 2, 0}, {REPLY, 0, 0}}; static const H handlers[] = {{1, 0, 8}}; @@ -4820,28 +4577,28 @@ static void message_fields(void) { memcpy(payload + 12, &count, 4); memcpy(payload + 16, "\xea\xb0\x80\xeb\x82\x98" "xyz", 9); size = 25; - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, size}) == PURE_OK); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, size}) == PURE_OK); CHECK(app.values[0].num == 5.5 && !strcmp(state_text(&app, 1), "\xea\xb0\x80\xeb\x82\x98") && replied(&app, "xyz")); pure_app_finish(&app); /* Empty variable fields. */ title_size = 0; memcpy(payload, &title_size, 4); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, 16}) == PURE_OK && !*state_text(&app, 1) && replied(&app, "")); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, 16}) == PURE_OK && !*state_text(&app, 1) && replied(&app, "")); pure_app_finish(&app); /* A title longer than what is there, a title that is not UTF-8, a number that is not finite, a short prefix. */ title_size = 10; memcpy(payload, &title_size, 4); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, 25}) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, 25}) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); title_size = 2; memcpy(payload, &title_size, 4); memcpy(payload + 16, "\xff\xfe", 2); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, 18}) == PURE_INVALID && app.fault == PURE_FAULT_ENCODING); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, 18}) == PURE_INVALID && app.fault == PURE_FAULT_ENCODING); title_size = 0; points = NAN; memcpy(payload, &title_size, 4); memcpy(payload + 4, &points, 8); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, 16}) == PURE_INVALID && app.fault == PURE_FAULT_NON_FINITE); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, 15}) == PURE_LIMIT); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, 16}) == PURE_INVALID && app.fault == PURE_FAULT_NON_FINITE); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, 15}) == PURE_LIMIT); CHECK(app.values[0].num == 5.5 && !*state_text(&app, 1)); /* failed messages changed nothing */ pure_app_stop(&app); /* A text field is text and a bytes field bytes. */ @@ -4867,18 +4624,18 @@ static void message_fields(void) { memcpy(payload + 16, body, sizeof(body) - 1); size = 16 + sizeof(body) - 1; uint32_t placed[8] = {16 + 23, 6, 0, 0, 16 + 9, 3, 0, 0}; - CHECK(pure_app_message_fields(&app, 0, NULL, -1, (PureSpan){payload, size}, placed) == PURE_OK); + CHECK(pure_app_message_fields(&app, 0, (PureSpan){payload, size}, placed) == PURE_OK); CHECK(app.values[0].num == 5.5 && !strcmp(state_text(&app, 1), "\xea\xb0\x80\xeb\x82\x98") && replied(&app, "xyz")); pure_app_finish(&app); /* A field that starts or ends past the payload, or a title that is not UTF-8 there. */ placed[0] = (uint32_t)size + 1; - CHECK(pure_app_message_fields(&app, 0, NULL, -1, (PureSpan){payload, size}, placed) == PURE_LIMIT); + CHECK(pure_app_message_fields(&app, 0, (PureSpan){payload, size}, placed) == PURE_LIMIT); placed[0] = (uint32_t)size - 2; placed[1] = 3; - CHECK(pure_app_message_fields(&app, 0, NULL, -1, (PureSpan){payload, size}, placed) == PURE_LIMIT); + CHECK(pure_app_message_fields(&app, 0, (PureSpan){payload, size}, placed) == PURE_LIMIT); placed[0] = 16 + 24; placed[1] = 2; - CHECK(pure_app_message_fields(&app, 0, NULL, -1, (PureSpan){payload, size}, placed) == PURE_INVALID && + CHECK(pure_app_message_fields(&app, 0, (PureSpan){payload, size}, placed) == PURE_INVALID && app.fault == PURE_FAULT_ENCODING); CHECK(app.values[0].num == 5.5); pure_app_stop(&app); @@ -4886,7 +4643,7 @@ static void message_fields(void) { /* Fields in groups: a name is a path of identifiers (user.name), and * never both a field and a group. */ static const F grouped[] = {{"user.name", PURE_FIELD_TEXT}, {"user.age", PURE_FIELD_U32}, {"plan", PURE_FIELD_TEXT}}; - static const I grouped_inputs[] = {{"post", NULL, PURE_KIND_MESSAGE, false, grouped, 3}}; + static const I grouped_inputs[] = {{"post", NULL, PURE_KIND_MESSAGE, grouped, 3}}; static const O grouped_code[] = {{FIELD, 1, 0}, {ST, 0, 0}, {FIELD, 0, 0}, {TSET, 1, 0}}; static const H grouped_handlers[] = {{1, 0, 4}}; Spec groups = spec; @@ -4896,12 +4653,12 @@ static void message_fields(void) { for (size_t i = 0; i < LEN(refused_paths); ++i) { F renamed[3] = {grouped[0], grouped[1], grouped[2]}; renamed[0].name = refused_paths[i]; - I renamed_inputs[1] = {{"post", NULL, PURE_KIND_MESSAGE, false, renamed, 3}}; + I renamed_inputs[1] = {{"post", NULL, PURE_KIND_MESSAGE, renamed, 3}}; groups.in = renamed_inputs; CHECK(load(groups, &module_b) == PURE_INVALID && strstr(error, "message field")); } F both[3] = {{"user", PURE_FIELD_TEXT}, grouped[1], grouped[2]}; - I both_inputs[1] = {{"post", NULL, PURE_KIND_MESSAGE, false, both, 3}}; + I both_inputs[1] = {{"post", NULL, PURE_KIND_MESSAGE, both, 3}}; groups.in = both_inputs; CHECK(load(groups, &module_b) == PURE_INVALID && strstr(error, "field and group")); } @@ -4944,7 +4701,7 @@ static PureResult buy(PureApp *app, uint32_t input, const char *item, uint32_t c size_t length = strlen(item); memcpy(payload, &count, 4); memcpy(payload + 4, item, length); - PureResult result = pure_app_message(app, input, NULL, -1, (PureSpan){payload, 4 + length}); + PureResult result = pure_app_message(app, input, (PureSpan){payload, 4 + length}); pure_app_finish(app); return result; } @@ -4966,9 +4723,9 @@ static void links(void) { static const S sender_nums[] = {{"n", 0}}; static const F order_fields[] = {{"item", PURE_FIELD_TEXT}, {"count", PURE_FIELD_U32}}; static const F ordered_fields[] = {{"item", PURE_FIELD_TEXT}, {"count", PURE_FIELD_U32}, {"total", PURE_FIELD_NUMBER}}; - static const I sender_inputs[] = {{"buy", NULL, PURE_KIND_MESSAGE, false, order_fields, 2}, - {"fail", NULL, PURE_KIND_MESSAGE, false, order_fields, 2}, - {"ordered", NULL, PURE_KIND_OUTPUT, false, ordered_fields, 3}}; + static const I sender_inputs[] = {{"buy", NULL, PURE_KIND_MESSAGE, order_fields, 2}, + {"fail", NULL, PURE_KIND_MESSAGE, order_fields, 2}, + {"ordered", NULL, PURE_KIND_OUTPUT, ordered_fields, 3}}; static const O sender_code[] = {{L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, {FIELD, 0, 0}, {FIELD, 1, 0}, {L, 0, 0}, {SEND, 2, 0}, /* buy */ {FIELD, 0, 0}, {FIELD, 1, 0}, {C, 0, 0}, {SEND, 2, 0}, {C, 1, 0}, {REQ, 0, 0}}; /* fail */ @@ -4978,7 +4735,7 @@ static void links(void) { static const S receiver_nums[] = {{"stock", 0}}; static const T receiver_texts[] = {{"last", 1, 0}}; static const char *const receiver_constants[] = {""}; - static const I receiver_inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, false, ordered_fields, 3}}; + static const I receiver_inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, ordered_fields, 3}}; static const O receiver_code[] = {{FIELD, 1, 0}, {L, 0, 0}, {ADD, 0, 0}, {ST, 0, 0}, {FIELD, 0, 0}, {TSET, 1, 0}, {FIELD, 1, 0}, {C, 0, 100}, {LT, 0, 0}, {REQ, 0, 0}}; /* refuses 100 or more */ static const H receiver_handlers[] = {{1, 0, 10}}; @@ -5007,7 +4764,7 @@ static void links(void) { /* Bad deliveries change nothing. */ CHECK(deliver(&b, "", 4, "x", 1) == PURE_INVALID && deliver(&b, "a b", 4, "x", 1) == PURE_INVALID); CHECK(deliver(&b, "other", 0, "x", 1) == PURE_INVALID && pure_app_deliver(&a, 2, "s", 1, sent, NULL) == PURE_INVALID); - CHECK(pure_app_message(&a, 2, NULL, -1, sent) == PURE_INVALID); + CHECK(pure_app_message(&a, 2, sent) == PURE_INVALID); /* Trimmed, a send is gone at once for the host and from the store at the next save. */ pure_app_trim(&a, 2, 1); CHECK(outbox_of(&a).count == 0 && pure_app_save(&a) == PURE_OK && pure_list_count(a.outbox) == 0); @@ -5167,7 +4924,7 @@ static void links(void) { /* Replacing: a module without the output is refused while sends are left * on it; once they are trimmed it replaces, and the output's number is * kept for a module that has it again. */ - static const I plain_inputs[] = {{"buy", NULL, PURE_KIND_MESSAGE, false, order_fields, 2}}; + static const I plain_inputs[] = {{"buy", NULL, PURE_KIND_MESSAGE, order_fields, 2}}; static const O plain_code[] = {{L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}}; static const H plain_handlers[] = {{1, 0, 4}}; Spec plain = {sender_nums, 1, plain_inputs, 1, NULL, 0, plain_handlers, 1, plain_code, LEN(plain_code), @@ -5269,7 +5026,7 @@ static void work_budget(void) { unsigned char payload[8]; for (uint32_t item = 0; item < 5; ++item) { put32(put32(payload, item), 2); - CHECK(pure_app_message(&app, 2, NULL, -1, (PureSpan){payload, 8}) == PURE_OK && app.work == 3); + CHECK(pure_app_message(&app, 2, (PureSpan){payload, 8}) == PURE_OK && app.work == 3); } pure_app_work(&app, 23 + 22 * 5 - 1); CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_LIMIT && app.fault == PURE_FAULT_WORK); @@ -5279,10 +5036,10 @@ static void work_budget(void) { for (size_t i = 0; i < 5; ++i) CHECK(pure_list_get(app.lists[W_STOCK], i, 1).num == 3); pure_app_work(&app, 2); put32(put32(payload, 9), 1); - CHECK(pure_app_message(&app, 2, NULL, -1, (PureSpan){payload, 8}) == PURE_LIMIT && app.fault == PURE_FAULT_WORK); + CHECK(pure_app_message(&app, 2, (PureSpan){payload, 8}) == PURE_LIMIT && app.fault == PURE_FAULT_WORK); CHECK(pure_list_count(app.lists[W_STOCK]) == 5); pure_app_work(&app, 0); /* the default */ - CHECK(pure_app_message(&app, 2, NULL, -1, (PureSpan){payload, 8}) == PURE_OK && pure_list_count(app.lists[W_STOCK]) == 6); + CHECK(pure_app_message(&app, 2, (PureSpan){payload, 8}) == PURE_OK && pure_list_count(app.lists[W_STOCK]) == 6); pure_app_stop(&app); pure_module_release(&module_a); @@ -5332,9 +5089,9 @@ static void memory_budget(void) { static const R records[] = {{"row", fields, 1}}; static const F value[] = {{"value", PURE_FIELD_NUMBER}}; static const I inputs[] = {KEY("add", "KeyA"), KEY("drop", "KeyD"), KEY("send", "KeyS"), - {"out", NULL, PURE_KIND_OUTPUT, false, value, 1}, - {"take", NULL, PURE_KIND_MESSAGE, false, value, 1}, - {"answer", NULL, PURE_KIND_MESSAGE, false, value, 1}}; + {"out", NULL, PURE_KIND_OUTPUT, value, 1}, + {"take", NULL, PURE_KIND_MESSAGE, value, 1}, + {"answer", NULL, PURE_KIND_MESSAGE, value, 1}}; static const O code[] = { {L, 0, 0}, {LA, 1, 0}, /* add: rows.append(row { n }) */ {XF, 1, 0}, {LR, 1, 0}, /* drop: rows.remove(rows.first) */ @@ -5484,8 +5241,8 @@ static void output_meaning(void) { static const S nums[] = {{"n", 0}}; static const F was_fields[] = {{"debit", PURE_FIELD_NUMBER}, {"credit", PURE_FIELD_NUMBER}}; static const F now_fields[] = {{"credit", PURE_FIELD_NUMBER}, {"debit", PURE_FIELD_NUMBER}}; - static const I was_inputs[] = {KEY("tap", "KeyT"), {"pay", NULL, PURE_KIND_OUTPUT, false, was_fields, 2}}; - static const I now_inputs[] = {KEY("tap", "KeyT"), {"pay", NULL, PURE_KIND_OUTPUT, false, now_fields, 2}}; + static const I was_inputs[] = {KEY("tap", "KeyT"), {"pay", NULL, PURE_KIND_OUTPUT, was_fields, 2}}; + static const I now_inputs[] = {KEY("tap", "KeyT"), {"pay", NULL, PURE_KIND_OUTPUT, now_fields, 2}}; static const I none_inputs[] = {KEY("tap", "KeyT")}; static const O was_code[] = {{C, 0, 10}, {C, 0, 20}, {SEND, 1, 0}}; /* pay { debit: 10, credit: 20 } */ static const O now_code[] = {{C, 0, 20}, {C, 0, 10}, {SEND, 1, 0}}; /* the same, fields in the new order */ @@ -5543,8 +5300,8 @@ static const T bool_states[] = {{"on", 5, 1}, {"off", 5, 0}}; static const F set_fields[] = {{"on", PURE_FIELD_BOOL}, {"n", PURE_FIELD_NUMBER}}; static const F told_fields[] = {{"on", PURE_FIELD_BOOL}, {"big", PURE_FIELD_BOOL}, {"n", PURE_FIELD_NUMBER}}; static const I bool_inputs[] = {KEY("flip", "KeyF"), KEY("count", "KeyC"), KEY("check", "KeyK"), KEY("tell", "KeyT"), - {"set", NULL, PURE_KIND_MESSAGE, false, set_fields, 2}, - {"told", NULL, PURE_KIND_OUTPUT, false, told_fields, 3}}; + {"set", NULL, PURE_KIND_MESSAGE, set_fields, 2}, + {"told", NULL, PURE_KIND_OUTPUT, told_fields, 3}}; static const O bool_code[] = { {L, B_ON, 0}, {NOT, 0, 0}, {ST, B_ON, 0}, /* 0 flip: on = !on */ {L, B_ON, 0}, {JZ, 9, 0}, {L, B_N, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, B_N, 0}, /* 3 count: if on { n += 1 } */ @@ -5583,12 +5340,12 @@ static void bools(void) { unsigned char payload[9] = {1}; double five = 5; memcpy(payload + 1, &five, 8); - CHECK(pure_app_message(&app, 4, NULL, -1, (PureSpan){payload, 9}) == PURE_OK && app.values[B_ON].num == 1 && app.values[B_N].num == 5); + CHECK(pure_app_message(&app, 4, (PureSpan){payload, 9}) == PURE_OK && app.values[B_ON].num == 1 && app.values[B_N].num == 5); pure_app_finish(&app); payload[0] = 2; five = 6; memcpy(payload + 1, &five, 8); - CHECK(pure_app_message(&app, 4, NULL, -1, (PureSpan){payload, 9}) == PURE_LIMIT && app.values[B_N].num == 5); + CHECK(pure_app_message(&app, 4, (PureSpan){payload, 9}) == PURE_LIMIT && app.values[B_N].num == 5); pure_app_finish(&app); /* A send holds its bools as bytes. */ CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK); @@ -5800,8 +5557,8 @@ static const R item_i64_records[] = {{"Item", item_i64_fields, 2}}; static const F put_i64_fields[] = {{"id", PURE_FIELD_I64}, {"qty", PURE_FIELD_NUMBER}}; static const F echo_i64_fields[] = {{"id", PURE_FIELD_I64}}; static const I keyed_i64_inputs[] = {KEY("add", "KeyA"), KEY("low", "KeyL"), KEY("drop", "KeyD"), KEY("ask", "KeyK"), - {"put", NULL, PURE_KIND_MESSAGE, false, put_i64_fields, 2}, - {"echo", NULL, PURE_KIND_OUTPUT, false, echo_i64_fields, 1}}; + {"put", NULL, PURE_KIND_MESSAGE, put_i64_fields, 2}, + {"echo", NULL, PURE_KIND_OUTPUT, echo_i64_fields, 1}}; static Spec keyed_i64_spec(O *code) { O made[] = { @@ -5928,7 +5685,7 @@ static void integers(void) { double qty = 2.5; memcpy(payload, &least, 8); memcpy(payload + 8, &qty, 8); - CHECK(pure_app_message(&app, 4, NULL, -1, (PureSpan){payload, 16}) == PURE_OK && i64_view(&app, 2) == INT64_MIN); + CHECK(pure_app_message(&app, 4, (PureSpan){payload, 16}) == PURE_OK && i64_view(&app, 2) == INT64_MIN); pure_app_finish(&app); Outbox o = outbox_of(&app); CHECK(o.count == 1 && o.items[0].size == 8 && !memcmp(o.items[0].payload, &least, 8)); @@ -6228,7 +5985,7 @@ static void formats_and_held(void) { static const RF byte_fields[] = {{"v", 3, 0}}; static const R records[] = {{"Byte", byte_fields, 1}}; static const char *const constants[] = {"Az\xc3\xa9", ""}; - static const I inputs[] = {KEY("go", "KeyG"), KEY("grow", "KeyH"), {"ask", NULL, PURE_KIND_MESSAGE, false, NULL, 0}}; + static const I inputs[] = {KEY("go", "KeyG"), KEY("grow", "KeyH"), {"ask", NULL, PURE_KIND_MESSAGE, NULL, 0}}; static const O code[] = { /* 0 go: held5 = held5 + bytes(note); said = text(held5) */ {HLD, 5, 0}, {TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {CAT, 1, 0}, {HST, 5, 0}, {HLD, 5, 0}, {PURE_OP_TEXT_FROM, 0, 0}, @@ -6250,7 +6007,7 @@ static void formats_and_held(void) { CHECK(!strcmp(state_text(&app, 4), "Az\xc3\xa9")); /* held5 began empty again */ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY); CHECK(!strcmp(state_text(&app, 4), "Az\xc3\xa9")); /* nothing changed */ - CHECK(pure_app_message(&app, 2, NULL, -1, (PureSpan){NULL, 0}) == PURE_OK); + CHECK(pure_app_message(&app, 2, (PureSpan){NULL, 0}) == PURE_OK); Text replied = {{0}, 0}; CHECK(pure_app_reply(&app, text_span, &replied) == PURE_OK && !strcmp(replied.text, "Az\xc3\xa9")); pure_app_stop(&app); @@ -6648,7 +6405,7 @@ static void nested_rows(void) { static const T others[] = {{"order.nodes", 3 | (uint32_t)PURE_LIST_ROWS << 8, 1}, {"let.kids", 3 | (uint32_t)(PURE_LIST_ROWS | PURE_LIST_BOUND) << 8, 0}, {"total", PURE_STATE_I64, 0}}; static const F fields[] = {{"nodes", PURE_FIELD_ROWS | NODES << 8}}; - static const I inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, false, fields, 1}}; + static const I inputs[] = {{"order", NULL, PURE_KIND_MESSAGE, fields, 1}}; static const O code[] = { /* 0: for x in order.nodes { total += sum(k.v for k in x.kids) } */ {EACH, WALKING(NODES, 10), 0}, {ROW, NODES, 0}, {IG, ITEM(NODES, 1), 0}, {BIND_, KIDS, 0}, @@ -6669,7 +6426,7 @@ static void nested_rows(void) { size += put_node(payload + size, 2, second, 1); size += put_node(payload + size, 3, NULL, 0); for (int round = 1; round <= 3; ++round) { /* each event begins with its lists and theirs empty */ - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, size}) == PURE_OK); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, size}) == PURE_OK); CHECK(app.values[TOTAL].i64 == 21 * round && app.values[0].num == (round == 1 ? 3 : round == 2 ? 33 : 333)); CHECK(pure_list_count(app.lists[NODES]) == 0 && app.nested.count == 2); /* the third node's kids are none */ } @@ -6678,11 +6435,11 @@ static void nested_rows(void) { unsigned char broken[64]; memcpy(broken, payload, size); put32(broken + 8, 1000); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){broken, size}) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); + CHECK(pure_app_message(&app, 0, (PureSpan){broken, size}) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); for (size_t k = 0; k < LEN(cuts); ++k) - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, cuts[k]}) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, cuts[k]}) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); CHECK(app.values[TOTAL].i64 == 63 && app.values[0].num == 333 && pure_list_count(app.lists[NODES]) == 0 && !app.nested.count); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){payload, size}) == PURE_OK && app.values[TOTAL].i64 == 84); + CHECK(pure_app_message(&app, 0, (PureSpan){payload, size}) == PURE_OK && app.values[TOTAL].i64 == 84); pure_app_stop(&app); printf("{\"nested_rows\":{\"rounds\":3,\"refused\":%zu}}\n", LEN(cuts) + 1); } @@ -6699,8 +6456,8 @@ static void bytes_fields(void) { static const R before[] = {{"Item", text_fields, 1}}, after[] = {{"Item", bytes_fields_, 2}}; static const T others[] = {{"items", 3, 0}, {"blob", PURE_STATE_TEXT, 0}}; static const F text_in[] = {{"t", PURE_FIELD_TEXT}}, bytes_in[] = {{"b", PURE_FIELD_BYTES}}; - static const I inputs_a[] = {{"put", NULL, PURE_KIND_MESSAGE, false, text_in, 1}}; - static const I inputs_b[] = {{"put", NULL, PURE_KIND_MESSAGE, false, bytes_in, 1}}; + static const I inputs_a[] = {{"put", NULL, PURE_KIND_MESSAGE, text_in, 1}}; + static const I inputs_b[] = {{"put", NULL, PURE_KIND_MESSAGE, bytes_in, 1}}; /* A: items.append(Item { t: put.t }); n = items.count */ static const O code_a[] = {{FIELD, 0, 0}, {LA, ITEMS, 0}, {LC, ITEMS, 0}, {ST, 0, 0}}; /* B: items.append(Item { t: put.b, b: bytes("zz") + put.b }); n = items.last.b's length */ @@ -6714,12 +6471,12 @@ static void bytes_fields(void) { if (load(a, &module_a) != PURE_OK || load(b, &module_b) != PURE_OK) { fprintf(stderr, "bytes fields: %s\n", error); abort(); } PureApp app; CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK); - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){(const unsigned char *)"héllo", 6}) == PURE_OK); + CHECK(pure_app_message(&app, 0, (PureSpan){(const unsigned char *)"héllo", 6}) == PURE_OK); CHECK(pure_app_replace(&app, &module_b) == PURE_OK); PureCell t = pure_list_get(app.lists[ITEMS], 0, 0), zz = pure_list_get(app.lists[ITEMS], 0, 1); CHECK(t.size == 6 && !memcmp(t.text, "héllo", 6) && zz.size == 2 && !memcmp(zz.text, "zz", 2)); static const unsigned char raw[] = {0xFF, 0x00, 0xC3, 0x28, 0x80}; /* no UTF-8 */ - CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){raw, sizeof(raw)}) == PURE_OK && app.values[0].num == 2 + sizeof(raw)); + CHECK(pure_app_message(&app, 0, (PureSpan){raw, sizeof(raw)}) == PURE_OK && app.values[0].num == 2 + sizeof(raw)); PureCell back = pure_list_get(app.lists[ITEMS], 1, 0), joined = pure_list_get(app.lists[ITEMS], 1, 1); CHECK(back.size == sizeof(raw) && !memcmp(back.text, raw, sizeof(raw)) && joined.size == 2 + sizeof(raw) && !memcmp(joined.text, "zz", 2) && !memcmp(joined.text + 2, raw, sizeof(raw))); @@ -7133,6 +6890,6 @@ int main(void) { stored_states(); deferred(); printf("{\"sizeof_module\":%zu,\"sizeof_app\":%zu}\n", sizeof(PureModule), sizeof(PureApp)); - puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-bools-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing-marks-folds-filters-keyed-lists-key-sets-arrays-text-order-message-fields-timers-now-snapshots-links\"}"); + puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-bools-replace-by-name-rejections-mutations-messages-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing-marks-folds-filters-keyed-lists-key-sets-arrays-text-order-message-fields-timers-now-snapshots-links\"}"); return 0; } |
