aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/module-save.c55
-rw-r--r--core/module-save.h5
-rw-r--r--core/module.c48
-rw-r--r--core/module.h7
-rw-r--r--core/test-module.c106
5 files changed, 198 insertions, 23 deletions
diff --git a/core/module-save.c b/core/module-save.c
index 1055b4f..6e204d1 100644
--- a/core/module-save.c
+++ b/core/module-save.c
@@ -131,25 +131,54 @@ PURE_COLD static bool named(const unsigned char *bytes, size_t size, size_t *at,
return true;
}
-PURE_COLD PureResult pure_save_open_text(PureApp *app, const PureModule *m, uint32_t state, PureDraft **out,
+/* A bytes state's mark among the values (028): its kind past every state type, its name, 8 bytes of 0. A text state's
+ * stored bytes that are no UTF-8 are then a move from bytes (its initial value), not a store gone wrong. */
+enum { SAVED_BYTES = 7 };
+static bool bytes_state(const PureModule *m, uint32_t i) {
+ return m->state_type[i] == PURE_STATE_TEXT && m->state_flags[i] & PURE_TEXT_BYTES;
+}
+
+/* Whether the values record marks the state of that name as bytes (028). */
+PURE_COLD static bool marked_bytes(PureApp *app, const char *name) {
+ if (!app->stored_values && open_state(app, "", "values", &app->stored_values) != PURE_OK) return false;
+ unsigned char *bytes;
+ size_t size;
+ if (!pure_draft_revision(app->stored_values) || read_whole(app->stored_values, &bytes, &size) != PURE_OK) return false;
+ bool found = false, ok = size >= 4 && !memcmp(bytes, values_magic, 4);
+ for (size_t at = 4; ok && !found && at < size; at += 8) {
+ char entry[PURE_MODULE_NAME];
+ uint8_t type;
+ if (!(ok = named(bytes, size, &at, 8, &type, entry))) break;
+ found = type == SAVED_BYTES && !strcmp(entry, name);
+ }
+ free(bytes);
+ return found;
+}
+
+PURE_COLD PureResult pure_save_open_text(PureApp *app, const PureModule *m, uint32_t state, bool anew, PureDraft **out,
PureVersion *saved) {
PureResult result = open_state(app, "text-", m->state_names[state], out);
if (result != PURE_OK) return result;
*saved = pure_draft_version(*out);
- if (!pure_draft_revision(*out)) {
- uint32_t constant = m->state_ref[state];
- PureSpan initial = {m->constant_data + m->constant_offset[constant], m->constant_length[constant]};
- result = pure_draft_splice(*out, 0, pure_draft_size(*out), initial);
- *saved = unsaved;
- } else {
+ bool initial = anew || !pure_draft_revision(*out);
+ if (!initial && !bytes_state(m, state)) { /* a text's bytes are UTF-8, or a bytes state's were (a move: anew) */
unsigned char *bytes;
size_t size;
result = read_whole(*out, &bytes, &size);
if (result == PURE_OK) {
- if (!pure_utf8_valid((PureSpan){bytes, size})) result = PURE_STORE_ERROR;
+ if (!pure_utf8_valid((PureSpan){bytes, size})) {
+ if (marked_bytes(app, m->state_names[state])) initial = true;
+ else result = PURE_STORE_ERROR;
+ }
free(bytes);
}
}
+ if (result == PURE_OK && initial) {
+ uint32_t constant = m->state_ref[state];
+ PureSpan value = {m->constant_data + m->constant_offset[constant], m->constant_length[constant]};
+ result = pure_draft_splice(*out, 0, pure_draft_size(*out), value);
+ *saved = unsaved;
+ }
if (result != PURE_OK) { pure_draft_close(*out); *out = NULL; }
return result;
}
@@ -789,11 +818,11 @@ static size_t encode_values(const PureModule *m, const PureValue *values, unsign
size_t at = 4;
memcpy(out, values_magic, 4);
for (uint32_t i = 0; i < m->states; ++i) {
- if (!scalar(m->state_type[i]) || pure_state_transient(m, i)) continue;
+ if ((!scalar(m->state_type[i]) && !bytes_state(m, i)) || pure_state_transient(m, i)) continue;
size_t length = strlen(m->state_names[i]);
- uint64_t bits;
- memcpy(&bits, &values[i], sizeof(bits));
- out[at++] = m->state_type[i];
+ uint64_t bits = 0;
+ if (!bytes_state(m, i)) memcpy(&bits, &values[i], sizeof(bits));
+ out[at++] = bytes_state(m, i) ? (unsigned char)SAVED_BYTES : m->state_type[i];
out[at++] = (unsigned char)length;
memcpy(out + at, m->state_names[i], length);
at += length;
@@ -813,6 +842,8 @@ PURE_COLD PureResult pure_save_open_values(PureApp *app, const PureModule *m, Pu
if (scalar(m->state_type[i]) && !pure_state_transient(m, i)) {
scalars = true;
fresh = fresh || kept[i] < 0;
+ } else if (bytes_state(m, i) && !pure_state_transient(m, i)) {
+ scalars = true; /* its mark is among the values */
}
if (!scalars) return PURE_OK;
PureResult result = app->stored_values ? PURE_OK : open_state(app, "", "values", &app->stored_values);
diff --git a/core/module-save.h b/core/module-save.h
index 44d3b95..2a82d10 100644
--- a/core/module-save.h
+++ b/core/module-save.h
@@ -10,7 +10,10 @@
bool pure_save_enabled(const PureApp *);
/* A text state of m: its stored value, or its initial constant when none. */
-PureResult pure_save_open_text(PureApp *, const PureModule *m, uint32_t state, PureDraft **out, PureVersion *saved);
+/* A text state's draft in the store, its initial value where it has none, where anew says (a move that keeps nothing,
+ * 028), or where its bytes are no UTF-8 but the values record marks it bytes; store_error where they are no UTF-8
+ * otherwise. A bytes state's asks no UTF-8. */
+PureResult pure_save_open_text(PureApp *, const PureModule *m, uint32_t state, bool anew, PureDraft **out, PureVersion *saved);
/* Stored lists (module-save.c): at a start, the log opened and read for the
* lists (pure_save_open_log), each list opened from its image with its
* logged changes made again (pure_save_open_list; saved says what its image
diff --git a/core/module.c b/core/module.c
index 64ec8f6..621a510 100644
--- a/core/module.c
+++ b/core/module.c
@@ -575,6 +575,10 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
(truth ? !canonical_bool(op->value) : integer ? false : constant ? !isfinite(op->value) : op->integer != 0))
REFUSE("non-canonical operand");
if (!names_arg(m, kind, arg, shape, row, outer)) REFUSE("operand names no such state, field, constant or unit");
+ /* A bytes state (028) is read whole and set whole: no anchor is in it, so no op of a text's anchors takes it. */
+ if ((kind == A_STATE + PURE_STATE_TEXT || kind == A_IN_TEXT) && op->op != PURE_OP_TEXT_LOAD && op->op != PURE_OP_TEXT_SET &&
+ m->state_flags[arg & 0xFFFF] & PURE_TEXT_BYTES)
+ REFUSE("anchor in bytes");
/* A keyed list places its items by key, and an item's key stays. */
if (op->op == PURE_OP_LIST_INSERT && m->state_key[arg]) REFUSE("insert by position into a keyed list");
/* A walk ends: no list a walk walks grows while it runs, and only the innermost walk's may shrink (it moves with it). */
@@ -672,6 +676,8 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
need[0] = (arg & 3) == 1 ? T_BOOL : T_I64;
} else if ((op->op == PURE_OP_CONCAT && arg) || (op->op == PURE_OP_TEXT_COMPARE && arg & PURE_COMPARE_BYTES)) {
need[0] = need[1] = T_BYTES;
+ } else if (op->op == PURE_OP_TEXT_SET && m->state_flags[arg] & PURE_TEXT_BYTES) {
+ need[0] = T_BYTES; /* a bytes state's whole value (028) */
} else if (op->op == PURE_OP_BYTES_SLICE && arg) {
need[1] = need[2] = T_I64; /* the positions i64s */
} else if (op->op == PURE_OP_BYTES_STORE && (arg & 0xFF) >= PURE_HELD_TEXT) {
@@ -729,7 +735,9 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (out == T_STATE) out = state_type(m, arg);
if (truth) out = T_BOOL;
if (integer) out = T_I64;
- if ((op->op == PURE_OP_CONCAT && arg) || (op->op == PURE_OP_BYTES_LOAD && (arg & 0xFF) < PURE_HELD_TEXT)) out = T_BYTES;
+ if ((op->op == PURE_OP_CONCAT && arg) || (op->op == PURE_OP_BYTES_LOAD && (arg & 0xFF) < PURE_HELD_TEXT) ||
+ (op->op == PURE_OP_TEXT_LOAD && m->state_flags[arg] & PURE_TEXT_BYTES))
+ out = T_BYTES;
else if (op->op == PURE_OP_BYTES_LOAD) out = T_TEXT;
if (op->op == PURE_OP_CALL) /* its results */
for (uint32_t k = 0, first = m->function[arg].first + m->function[arg].params; k < m->function[arg].results; ++k) {
@@ -1799,11 +1807,12 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
* be in a list (below). */
static const uint8_t held_as[] = {[PURE_MODULE_NUM] = PURE_STATE_NUM, [PURE_MODULE_TEXT] = PURE_STATE_TEXT,
[PURE_MODULE_ANCHOR] = PURE_STATE_CURSOR, [PURE_MODULE_LIST] = PURE_STATE_LIST,
- [PURE_MODULE_BOOL] = PURE_STATE_BOOL, [PURE_MODULE_I64] = PURE_STATE_I64};
+ [PURE_MODULE_BOOL] = PURE_STATE_BOOL, [PURE_MODULE_I64] = PURE_STATE_I64,
+ [PURE_MODULE_BYTES] = PURE_STATE_TEXT};
const unsigned char *states_at = at;
for (uint32_t i = 0; i < out->states; ++i, at += STATE_BYTES) {
uint32_t type = read32(at + 4);
- if ((type & ~(uint32_t)PURE_STATE_TRANSIENT) > PURE_MODULE_I64) REFUSE("state type", i);
+ if ((type & ~(uint32_t)PURE_STATE_TRANSIENT) > PURE_MODULE_BYTES) REFUSE("state type", i);
out->state_type[i] = held_as[type & 0xFF];
out->state_transient[i] = type & PURE_STATE_TRANSIENT;
/* A number's, a bool's or an i64's value is its 8 bytes; others have flags:
@@ -1833,7 +1842,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if ((flags && !(cursor && flags == PURE_CURSOR_STAYS) && !(list && key <= PURE_RECORD_FIELDS)) ||
read32(at + 8) > UINT16_MAX) REFUSE("state reference", i);
out->state_ref[i] = (uint16_t)read32(at + 8);
- out->state_flags[i] = cursor ? (uint8_t)flags : (uint8_t)((rows ? PURE_LIST_ROWS >> 8 : 0) | (bound ? PURE_LIST_BOUND >> 8 : 0));
+ out->state_flags[i] = cursor ? (uint8_t)flags : list ? (uint8_t)((rows ? PURE_LIST_ROWS >> 8 : 0) | (bound ? PURE_LIST_BOUND >> 8 : 0))
+ : (type & 0xFF) == PURE_MODULE_BYTES ? (uint8_t)PURE_TEXT_BYTES : 0;
out->state_key[i] = (uint8_t)key;
}
}
@@ -1960,7 +1970,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
at = fields_at + (size_t)out->record_fields * RECORD_FIELD_BYTES;
for (uint32_t i = 0; i < out->states; ++i)
if ((out->state_type[i] == PURE_STATE_TEXT && out->state_ref[i] >= out->constants) ||
- (out->state_type[i] == PURE_STATE_CURSOR && !state_of(out, out->state_ref[i], PURE_STATE_TEXT)) ||
+ (out->state_type[i] == PURE_STATE_CURSOR &&
+ (!state_of(out, out->state_ref[i], PURE_STATE_TEXT) || out->state_flags[out->state_ref[i]] & PURE_TEXT_BYTES)) ||
(out->state_type[i] == PURE_STATE_LIST &&
(out->state_ref[i] >= out->records || out->state_key[i] > out->record[out->state_ref[i]].count ||
(out->state_key[i] &&
@@ -4981,6 +4992,23 @@ PURE_COLD static PureResult new_text(PureDraft **out, const PureModule *m, uint3
}
/* A stored draft given a transient text's bytes, to be saved (saved: none yet). */
+/* Whether a draft's bytes are UTF-8 (a bytes state's, before it is a text's: 028). */
+typedef struct { unsigned char *bytes; size_t used; } Drafted;
+static bool drafted(void *context, PureSpan span) {
+ Drafted *d = context;
+ if (span.size) memcpy(d->bytes + d->used, span.data, span.size);
+ d->used += span.size;
+ return true;
+}
+PURE_COLD static bool draft_utf8(const PureDraft *draft) {
+ size_t size = pure_draft_size(draft);
+ Drafted into = {malloc(size ? size : 1), 0};
+ bool valid = into.bytes && pure_draft_read(draft, 0, size, drafted, &into) == PURE_OK && into.used == size &&
+ pure_utf8_valid((PureSpan){into.bytes, size});
+ free(into.bytes);
+ return valid;
+}
+
PURE_COLD static PureResult copy_text(PureDraft *to, const PureDraft *from, PureVersion *saved) {
size_t size = pure_draft_size(from);
unsigned char *bytes = size ? malloc(size) : NULL;
@@ -6445,17 +6473,21 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module)
/* A number, a bool and an i64 carry over each other when the value fits (pure_cell_as), else start anew. */
uint8_t was = previous >= 0 ? old->state_type[previous] : 0, now = module->state_type[i];
bool valued = pure_value_kind(was) != 0xFF && pure_value_kind(now) != 0xFF;
- kept[i] = previous >= 0 && (was == now || valued) ? previous : -1;
+ /* A text and bytes (028) carry over each other when exact: bytes become a text when they are UTF-8, else it is new. */
+ bool anew = previous >= 0 && was == PURE_STATE_TEXT && now == PURE_STATE_TEXT &&
+ old->state_flags[previous] & PURE_TEXT_BYTES && !(module->state_flags[i] & PURE_TEXT_BYTES) &&
+ !draft_utf8(app->texts[previous]);
+ kept[i] = previous >= 0 && (was == now || valued) && !anew ? previous : -1;
if (kept[i] >= 0) saved[i] = app->saved[kept[i]];
/* A transient state is never stored (018): one that was stored is now as a dropped one is. */
bool transient = pure_state_transient(module, i), was_transient = kept[i] >= 0 && pure_state_transient(old, kept[i]);
if (module->state_type[i] == PURE_STATE_TEXT) {
if (kept[i] >= 0 && (transient || !was_transient || !stored)) texts[i] = app->texts[kept[i]];
else if (result == PURE_OK && kept[i] >= 0 && /* a text stored from now on: its draft in the store holds it */
- (result = pure_save_open_text(app, module, i, &texts[i], &saved[i])) == PURE_OK)
+ (result = pure_save_open_text(app, module, i, false, &texts[i], &saved[i])) == PURE_OK)
result = copy_text(texts[i], app->texts[kept[i]], &saved[i]);
else if (result == PURE_OK)
- result = stored && !transient ? pure_save_open_text(app, module, i, &texts[i], &saved[i]) :
+ result = stored && !transient ? pure_save_open_text(app, module, i, anew, &texts[i], &saved[i]) :
new_text(&texts[i], module, module->state_ref[i]);
} else if (module->state_type[i] == PURE_STATE_LIST) {
if (result != PURE_OK) continue;
diff --git a/core/module.h b/core/module.h
index 6fad0ec..43d9ccb 100644
--- a/core/module.h
+++ b/core/module.h
@@ -259,8 +259,11 @@ typedef enum {
PURE_STATE_NUM = 0, PURE_STATE_TEXT = 1, PURE_STATE_CURSOR = 2, PURE_STATE_LIST = 3, PURE_STATE_INDEX = 4,
PURE_STATE_BOOL = 5, PURE_STATE_I64 = 6
} PureStateType;
-/* A state's type in a module: a number, a text, an anchor, a list, a bool or an i64. */
-enum { PURE_MODULE_NUM, PURE_MODULE_TEXT, PURE_MODULE_ANCHOR, PURE_MODULE_LIST, PURE_MODULE_BOOL, PURE_MODULE_I64 };
+/* A state's type in a module: a number, a text, an anchor, a list, a bool, an i64 or bytes (028: held as a text is, its
+ * flag PURE_TEXT_BYTES, UTF-8 asked of none of it). */
+enum { PURE_MODULE_NUM, PURE_MODULE_TEXT, PURE_MODULE_ANCHOR, PURE_MODULE_LIST, PURE_MODULE_BOOL, PURE_MODULE_I64,
+ PURE_MODULE_BYTES };
+#define PURE_TEXT_BYTES 1u /* a text state's flag (state_flags): its type is bytes (028) */
/* A number's, a bool's or an i64's state as a list cell's kind (PURE_CELL_...,
* for pure_cell_as), or 0xFF for any other state. */
static inline uint8_t pure_value_kind(uint8_t state_type) {
diff --git a/core/test-module.c b/core/test-module.c
index 8795bf8..9762c48 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -102,7 +102,9 @@ static unsigned char *put_double(unsigned char *p, double value) {
* module has their anchor (027's 4c), its type 2, and its instructions, which name the text or list (bits 0-15 of a
* MOVE's and a COUNT's argument: IN), so a test gives that where a step does not name it. */
#define IN(target, unit) ((uint32_t)(target) | (uint32_t)(unit) << PURE_ANCHOR_UNIT)
+#define T_BYTES_STATE 64 /* in a T's type: a bytes state (028), ref its constant as a text's */
static uint32_t module_type(uint32_t type) {
+ if (type == T_BYTES_STATE) return PURE_MODULE_BYTES;
static const uint8_t types[] = {[PURE_STATE_NUM] = PURE_MODULE_NUM, [PURE_STATE_TEXT] = PURE_MODULE_TEXT,
[PURE_STATE_CURSOR] = PURE_MODULE_ANCHOR, [PURE_STATE_LIST] = PURE_MODULE_LIST,
[PURE_STATE_INDEX] = PURE_MODULE_ANCHOR, [PURE_STATE_BOOL] = PURE_MODULE_BOOL,
@@ -6164,6 +6166,109 @@ static void bits_and_bytes(void) {
printf("{\"bits_and_bytes\":{\"bits\":%zu,\"reads\":%zu,\"refused\":%zu}}\n", LEN(cases), LEN(reads), LEN(refused));
}
+/* ---- Bytes alone as a state (docs/design/028) ----------------------------
+ * A bytes state is held as a text is (one draft) and read and set whole as
+ * bytes, UTF-8 asked of none of it: 0xff and 0x00 kept across a restart.
+ * Text and bytes of a name carry over each other when exact, on a replace
+ * and on a start from a store: a text always, bytes when UTF-8, else the
+ * initial value (the values record marks a bytes state, so a text's bytes
+ * that are no UTF-8 are a move, not a store gone wrong). Refused: an anchor
+ * in it, a cursor's op on it, a text set into it, its bytes into a text. */
+typedef struct { unsigned char *at; size_t used; } Gathered64;
+static bool gathered64(void *context, PureSpan span) {
+ Gathered64 *g = context;
+ if (g->used + span.size > 64) return false;
+ if (span.size) memcpy(g->at + g->used, span.data, span.size);
+ g->used += span.size;
+ return true;
+}
+static size_t draft_bytes(const PureApp *app, uint32_t state, unsigned char *out) {
+ Gathered64 g = {out, 0};
+ return pure_draft_read(app->texts[state], 0, pure_draft_size(app->texts[state]), gathered64, &g) == PURE_OK ? g.used
+ : (size_t)-1;
+}
+
+static void bytes_states(void) {
+ enum { TS = PURE_OP_TEXT_SET, CAT_ = PURE_OP_CONCAT, BYTE_ = PURE_OP_BYTE, LEN_ = PURE_OP_LEN, CH = PURE_OP_CHARS };
+ static const S nums[] = {{"n", 0}};
+ static const char *const constants[] = {"Az\xc3\xa9", ""};
+ static const I inputs[] = {KEY("go", "KeyG"), KEY("set", "KeyS")};
+ static const H handlers[] = {{1, 0, 11}, {3, 11, 6}}, text_handlers[] = {{1, 0, 3}, {3, 3, 5}};
+ /* As bytes: go appends ff 00 (n its length), set gives it "Az\xc3\xa9"'s bytes. */
+ const O as_bytes[] = {{TL, 1, 0}, IC(0xFF), {BYTE_, 0, 0}, {CAT_, 1, 0}, IC(0), {BYTE_, 0, 0}, {CAT_, 1, 0}, {TS, 1, 0},
+ {TL, 1, 0}, {LEN_, 0, 0}, {ST, 0, 0},
+ {TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {TS, 1, 0}, {TL, 1, 0}, {LEN_, 0, 0}, {ST, 0, 0}};
+ /* As a text: go counts its characters, set gives it "Az\xc3\xa9". */
+ static const O as_text[] = {{TL, 1, 0}, {CH, 0, 0}, {ST, 0, 0}, {TK, 0, 0}, {TS, 1, 0}, {TL, 1, 0}, {CH, 0, 0}, {ST, 0, 0}};
+ static const T bytes_state[] = {{"data", T_BYTES_STATE, 1}}, text_state[] = {{"data", 1, 1}};
+ Spec bytes_spec = {nums, 1, inputs, 2, NULL, 0, handlers, 2, as_bytes, LEN(as_bytes), NULL, 0, bytes_state, 1, constants,
+ 2, NULL, 0, NULL, 0, NULL, 0};
+ Spec text_spec = {nums, 1, inputs, 2, NULL, 0, text_handlers, 2, as_text, LEN(as_text), NULL, 0, text_state, 1, constants,
+ 2, NULL, 0, NULL, 0, NULL, 0};
+ static PureModule bytes_module, text_module;
+ if (load(bytes_spec, &bytes_module) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
+ if (load(text_spec, &text_module) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
+ CHECK(bytes_module.state_type[1] == PURE_STATE_TEXT && bytes_module.state_flags[1] & PURE_TEXT_BYTES &&
+ !(text_module.state_flags[1] & PURE_TEXT_BYTES));
+ unsigned char got[64];
+ static const unsigned char twice[] = {0xFF, 0, 0xFF, 0}, word[] = {'A', 'z', 0xC3, 0xA9};
+ /* Stored: 0xff and 0x00 kept across a restart. */
+ char *path = new_store();
+ PureApp app;
+ CHECK(pure_app_start(&app, &bytes_module, pure_tree_data(path)) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK && press(&app, 0) == PURE_OK && app.values[0].num == 4);
+ pure_app_stop(&app);
+ CHECK(pure_app_start(&app, &bytes_module, pure_tree_data(path)) == PURE_OK);
+ CHECK(draft_bytes(&app, 1, got) == 4 && !memcmp(got, twice, 4));
+ pure_app_stop(&app);
+ /* Bytes to a text across a start: no UTF-8, the record says bytes: its initial value (not a store gone wrong). */
+ CHECK(pure_app_start(&app, &text_module, pure_tree_data(path)) == PURE_OK);
+ CHECK(pure_draft_size(app.texts[1]) == 0 && press(&app, 1) == PURE_OK && app.values[0].num == 3);
+ pure_app_stop(&app);
+ /* Back to bytes: the text's bytes; then ff 00 after them; then to a text again: no UTF-8 now, initial again. */
+ CHECK(pure_app_start(&app, &bytes_module, pure_tree_data(path)) == PURE_OK);
+ CHECK(draft_bytes(&app, 1, got) == 4 && !memcmp(got, word, 4) && press(&app, 0) == PURE_OK && app.values[0].num == 6);
+ pure_app_stop(&app);
+ CHECK(pure_app_start(&app, &text_module, pure_tree_data(path)) == PURE_OK && pure_draft_size(app.texts[1]) == 0);
+ pure_app_stop(&app);
+ /* Bytes that are UTF-8 become the text. */
+ CHECK(pure_app_start(&app, &bytes_module, pure_tree_data(path)) == PURE_OK && press(&app, 1) == PURE_OK);
+ pure_app_stop(&app);
+ CHECK(pure_app_start(&app, &text_module, pure_tree_data(path)) == PURE_OK);
+ CHECK(!strcmp(state_text(&app, 1), "Az\xc3\xa9") && press(&app, 0) == PURE_OK && app.values[0].num == 3);
+ pure_app_stop(&app);
+ remove_store(path);
+ /* In memory, by replace: bytes that are no UTF-8 become a new text; UTF-8 ones the text; a text the bytes. */
+ CHECK(pure_app_start(&app, &bytes_module, (PureData){0}) == PURE_OK && press(&app, 0) == PURE_OK);
+ CHECK(pure_app_replace(&app, &text_module) == PURE_OK && pure_draft_size(app.texts[1]) == 0);
+ CHECK(press(&app, 1) == PURE_OK && pure_app_replace(&app, &bytes_module) == PURE_OK);
+ CHECK(draft_bytes(&app, 1, got) == 4 && !memcmp(got, word, 4) && press(&app, 1) == PURE_OK);
+ CHECK(pure_app_replace(&app, &text_module) == PURE_OK && !strcmp(state_text(&app, 1), "Az\xc3\xa9"));
+ pure_app_stop(&app);
+ /* Refused: a text set into it, its bytes into a text, a cursor's op on it, an anchor in it. */
+ const O refused[][2] = {
+ {{TK, 0, 0}, {TS, 1, 0}}, /* a text into the bytes state */
+ {{PURE_OP_CUR_START, 1, 0}, {PURE_OP_TEXT_LINE, 1, 0}}, /* a cursor of it */
+ };
+ for (size_t k = 0; k < LEN(refused); ++k) {
+ H handler = {1, 0, 2};
+ Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, refused[k], 2, NULL, 0, bytes_state, 1, constants, 2, NULL, 0,
+ NULL, 0, NULL, 0};
+ if (load(spec, &module_b) == PURE_OK) { fprintf(stderr, "bytes states: refused %zu loaded\n", k); abort(); }
+ }
+ static const O into_text[] = {{TL, 1, 0}, {TS, 2, 0}, {C, 0, 0}}; /* the bytes state's bytes into a text */
+ static const T both[] = {{"data", T_BYTES_STATE, 1}, {"note", 1, 1}};
+ H handler = {1, 0, 2};
+ Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, into_text, 2, NULL, 0, both, 2, constants, 2, NULL, 0, NULL, 0, NULL, 0};
+ CHECK(load(spec, &module_b) != PURE_OK);
+ static const T anchored[] = {{"data", T_BYTES_STATE, 1}, {"at", PURE_STATE_CURSOR, 1}};
+ static const O nothing[] = {{C, 0, 0}, {ST, 0, 0}};
+ Spec with_anchor = {nums, 1, inputs, 1, NULL, 0, &handler, 1, nothing, 2, NULL, 0, anchored, 2, constants, 2, NULL, 0, NULL, 0,
+ NULL, 0};
+ CHECK(load(with_anchor, &module_b) != PURE_OK);
+ printf("{\"bytes_states\":{\"starts\":7,\"replaces\":3,\"refused\":%zu}}\n", LEN(refused) + 2);
+}
+
/* ---- Bytes compared (after research 140's DNS and HTTP) --------------------
* TEXT_COMPARE with PURE_COMPARE_BYTES relates two bytes by their bytes, as
* it relates texts (a shorter before a longer it begins), UTF-8 asked of
@@ -7391,6 +7496,7 @@ int main(void) {
integers();
bits_and_bytes();
bytes_compared();
+ bytes_states();
formats_and_held();
lists_in_records();
functions_and_characters();