aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 20:54:10 +0100
committereunhhu <[email protected]>2026-10-18 20:54:10 +0100
commitda0d7514b058f1baa99a8bcc8f9e23b43cac12de (patch)
tree688106ea656946bd74f36259c2dd2316b9a429d8
parent936c08e3424c606855051ee86f25a7df0f162c62 (diff)
downloadpure-da0d7514b058f1baa99a8bcc8f9e23b43cac12de.tar.gz
pure-da0d7514b058f1baa99a8bcc8f9e23b43cac12de.zip
Module format v7, 027's 4d: every input a message: a module's inputs are messages, outputs and arguments (kinds 1 to 3), a message bound to what makes it (PureBinding: a key's press or its release, a text input, a timer, a frame, a pointing at a table), each binding's fields fixed (none; one text; one number, dt; one u32, the item's position, the table its view), one frame input at most; a handler's event its input's number; no DT or TEXT_INPUT (97 opcodes now 95, FIELD reads dt and the text), pure_app_input, frame, tick, text and point, pure_module_position, PureEdge or an app's ticks: every event is pure_app_message, saved after it unless deferred; pure-run sends each command's message to the input bound so (a release or a frame no input takes still an event, clearing the fault, so the workbench parity holds) and defers a stored app's saves past frames as before; pure-serve posts a press with its key's release, refuses timers, frames and pointings (no_input) and ticks timers as messages; holes closed: the tier found a handler's input by the old numbering, reading a message's number field as another input's (aup's numbers caught it), and serve's replies_borrow did too; module-probe presses as messages; unit tests: the test modules' builder (expand) writes the old shapes as bound messages, pointing over a pointing input and its table, stored frames deferred as a host defers them, a press's handler refused a FIELD; counts: b35 against b34 with batches BFS +0.03%, the rest within 0.001%, without batches BFS +0.12%, join +0.01%; the bank's queries (s34 on the 4c aup's module, s35 on 4d's, six each in turns) field -0.23%, found -0.14%, large -0.12%, in_credit +0.11%, the rest within 0.06%; 008 and 027
-rw-r--r--cli/pure-run.c47
-rw-r--r--core/module-jit.c8
-rw-r--r--core/module-run.h2
-rw-r--r--core/module.c137
-rw-r--r--core/module.h59
-rw-r--r--core/test-module.c652
-rw-r--r--docs/design/008-module-format.md196
-rw-r--r--docs/design/027-computation-core.md24
-rw-r--r--research/experiments/040-against-c/2026-10-18-counts-s34c.jsonl6
-rw-r--r--research/experiments/040-against-c/2026-10-18-counts-s35.jsonl6
-rw-r--r--research/experiments/041-batches/2026-10-18-counts.jsonl10
-rw-r--r--serve/serve.c34
-rw-r--r--tests/module-probe.c4
13 files changed, 659 insertions, 526 deletions
diff --git a/cli/pure-run.c b/cli/pure-run.c
index d3b7d1a..0d96f87 100644
--- a/cli/pure-run.c
+++ b/cli/pure-run.c
@@ -13,7 +13,8 @@
* every N commands (and at the end), so N events share one commit.
* down NAME | up NAME | frame [N] | type NAME HEX|- | message NAME HEX|- | tick NAME | time MS | replace PATH | save | show
* budget N (the work an event may do) | memory N (what the states may hold) | work (the last event's work)
- * (type gives a text input its bytes, message a message input its payload, laid out as the module says;
+ * (027's 4d: down and up a key's press and release, type a text's, tick a timer's and frame a frame's message, each by
+ * the input bound so; type gives a text input its bytes, message a message input its payload, laid out as the module says;
* message NAME @PATH gives it the bytes of the file at PATH, of any size.)
* Text states and text views print as JSON strings; cursors as byte positions;
* lists as arrays of objects (a bytes field as a string of its hex digits); indices as positions; a table view as an
@@ -135,6 +136,9 @@ static void show_value(PureApp *app, uint32_t view, size_t row) {
else printf("%.17g", value);
}
+/* The frames that ran, and the device events no message takes (a release, a frame): a surface's events all the same. */
+static unsigned long long ticks, untaken;
+
static void show(PureApp *app, const char *failure) {
const PureModule *m = app->module;
printf("{\"states\":{");
@@ -170,10 +174,17 @@ static void show(PureApp *app, const char *failure) {
putchar(']');
}
printf("},\"tick\":%llu,\"successfulEvents\":%llu,\"fault\":\"%s\",\"error\":%s%s%s}\n",
- (unsigned long long)app->ticks, (unsigned long long)app->events, pure_fault_name(app->fault),
+ ticks, (unsigned long long)app->events + untaken, pure_fault_name(app->fault),
failure ? "\"" : "", failure ? failure : "null", failure ? "\"" : "");
}
+/* The input bound so (027's 4d) with the key of input key (a press's release), or -1. */
+static int bound(const PureModule *m, uint8_t binding, int key) {
+ for (uint32_t i = 0; i < m->inputs; ++i)
+ if (m->input[i].binding == binding && (key < 0 || !strcmp(m->input_keys[i], m->input_keys[key]))) return (int)i;
+ return -1;
+}
+
static int hex(char c) {
return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
}
@@ -192,7 +203,8 @@ int main(int argc, char **argv) {
PureApp app;
PureResult started = pure_app_start(&app, &modules[active], stored ? pure_tree_data(argv[3]) : (PureData){0});
if (started != PURE_OK) { fprintf(stderr, "cannot start (%d)\n", (int)started); return 2; }
- if (batch && !pure_app_defer(&app, true)) { fputs("cannot defer saving\n", stderr); return 2; }
+ /* Frames wait for the next event's save (or the end's, or save): a stored app saves when the rest come. */
+ if (stored && !pure_app_defer(&app, true)) { fputs("cannot defer saving\n", stderr); return 2; }
long unsaved = 0;
static char line[1 << 16];
static unsigned char text[1 << 15];
@@ -202,11 +214,13 @@ int main(int argc, char **argv) {
const char *failure = NULL;
line[strcspn(line, "\r\n")] = 0;
if (sscanf(line, "%15s %255s %65535s", command, argument, data) < 1) continue;
- if (!strcmp(command, "down") || !strcmp(command, "up")) {
+ bool frame = false;
+ if (!strcmp(command, "down") || !strcmp(command, "up")) { /* a key's press, or its release when a message takes it */
int input = pure_module_input(app.module, argument);
- if (input < 0) failure = "unknown input";
- else if (pure_app_input(&app, (uint32_t)input, command[0] == 'd' ? PURE_EDGE_DOWN : PURE_EDGE_UP) != PURE_OK)
- failure = "event failed";
+ if (input < 0 || app.module->input[input].binding != PURE_BIND_PRESS) failure = "unknown input";
+ else if (command[0] == 'u') input = bound(app.module, PURE_BIND_RELEASE, input);
+ if (!failure && input < 0) ++untaken, app.fault = PURE_FAULT_NONE; /* an event that runs nothing */
+ else if (!failure && pure_app_message(&app, (uint32_t)input, (PureSpan){0}) != PURE_OK) failure = "event failed";
} else if (!strcmp(command, "type") || !strcmp(command, "message")) {
int input = pure_module_input(app.module, argument);
size_t length = strcmp(data, "-") ? strlen(data) / 2 : 0;
@@ -225,14 +239,12 @@ int main(int argc, char **argv) {
}
if (failure) {
} else if (input < 0) failure = "unknown input";
- else if ((command[0] == 't' ? pure_app_text(&app, (uint32_t)input, given)
- : pure_app_message(&app, (uint32_t)input, given)) != PURE_OK)
- failure = "event failed";
+ else if (pure_app_message(&app, (uint32_t)input, given) != PURE_OK) failure = "event failed";
free(read);
} else if (!strcmp(command, "tick")) {
int input = pure_module_input(app.module, argument);
if (input < 0) failure = "unknown input";
- else if (pure_app_tick(&app, (uint32_t)input) != PURE_OK) failure = "event failed";
+ else if (pure_app_message(&app, (uint32_t)input, (PureSpan){0}) != PURE_OK) failure = "event failed";
} else if (!strcmp(command, "time")) {
char *end;
double now = strtod(argument, &end);
@@ -240,9 +252,15 @@ int main(int argc, char **argv) {
else app.now = now;
} else if (!strcmp(command, "frame")) {
long count = *argument ? strtol(argument, NULL, 10) : 1;
+ int input = bound(app.module, PURE_BIND_FRAME, -1);
+ double dt = 1.0 / 60.0; /* its one field */
+ frame = true;
if (count < 1 || count > 600) failure = "frame count";
- for (long i = 0; !failure && i < count; ++i)
- if (pure_app_frame(&app) != PURE_OK) failure = "event failed";
+ for (long i = 0; !failure && i < count; ++i) {
+ if (input >= 0 && pure_app_message(&app, (uint32_t)input, (PureSpan){(const unsigned char *)&dt, 8}) != PURE_OK)
+ failure = "event failed";
+ else if (++ticks, input < 0) ++untaken, app.fault = PURE_FAULT_NONE;
+ }
} else if (!strcmp(command, "replace")) {
if (!load(argument, &modules[!active])) failure = "invalid module";
else if (pure_app_replace(&app, &modules[!active]) != PURE_OK) failure = "replace failed";
@@ -264,7 +282,8 @@ int main(int argc, char **argv) {
if (batch && strcmp(command, "show") && ++unsaved == batch) {
unsaved = 0;
if (pure_app_save(&app) != PURE_OK) failure = "save failed";
- }
+ } else if (stored && !batch && !frame && strcmp(command, "show") && pure_app_save(&app) != PURE_OK)
+ failure = "save failed";
if (failure) status = 1;
show(&app, failure);
}
diff --git a/core/module-jit.c b/core/module-jit.c
index 3a3a354..5ba9f03 100644
--- a/core/module-jit.c
+++ b/core/module-jit.c
@@ -2386,7 +2386,7 @@ static uint32_t changes(const PureModule *m, const PureOp *op) {
switch (op->op) {
case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: return UINT32_C(1) << (op->arg & 31);
case PURE_OP_ITEM_SET: return UINT32_C(1) << (list_of(op->arg) & 31);
- case PURE_OP_CONST: case PURE_OP_LOAD: case PURE_OP_DT: case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL:
+ case PURE_OP_CONST: case PURE_OP_LOAD: case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL:
case PURE_OP_DIV: case PURE_OP_NEG: case PURE_OP_LT: case PURE_OP_LE: case PURE_OP_GT: case PURE_OP_GE: case PURE_OP_EQ:
case PURE_OP_NE: case PURE_OP_AND: case PURE_OP_OR: case PURE_OP_NOT: case PURE_OP_MIN: case PURE_OP_MAX: case PURE_OP_CLAMP:
case PURE_OP_STORE: case PURE_OP_JZ: case PURE_OP_JMP: case PURE_OP_FIELD: case PURE_OP_LIST_COUNT: case PURE_OP_ITEM_GET:
@@ -5556,10 +5556,8 @@ static void tier_free(Tier *t) {
/* The handler's message input the block is, or -1. */
static int tier_input(const PureModule *m, PureBlock block) {
for (uint32_t i = 0; i < m->handlers; ++i)
- if (m->handler[i].code.start == block.start && m->handler[i].code.length == block.length && m->handler[i].event) {
- int input = (int)((m->handler[i].event - 1) / 2);
- return m->input[input].kind == PURE_KIND_MESSAGE ? input : -1;
- }
+ if (m->handler[i].code.start == block.start && m->handler[i].code.length == block.length)
+ return (int)m->handler[i].event; /* a message input's (027's 4d) */
return -1;
}
diff --git a/core/module-run.h b/core/module-run.h
index ceaa902..cff3f71 100644
--- a/core/module-run.h
+++ b/core/module-run.h
@@ -107,7 +107,7 @@ typedef struct {
const char *key; /* a keyed message's key */
int64_t base; /* the revision a message's edit was made against, or -1 */
PureArena *arena;
- double dt;
+
size_t row; /* the row a column is evaluated for (in a fold, the fold's item) */
size_t outer; /* in a fold, the row of the column it runs in */
Walk *walk; /* the walk the code runs in, or NULL */
diff --git a/core/module.c b/core/module.c
index 55923b2..2fbd37c 100644
--- a/core/module.c
+++ b/core/module.c
@@ -25,7 +25,7 @@ enum { FROM_PAYLOAD, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA, FROM_ARGUM
/* 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_TEXT = 16, IN_ROW = 32, IN_OUTER = 64, IN_ARGUMENTS = 128,
+enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, 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 +
@@ -72,7 +72,6 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_NE] = COMPARE, [PURE_OP_AND] = LOGIC(2), [PURE_OP_OR] = LOGIC(2), [PURE_OP_NOT] = LOGIC(1),
[PURE_OP_MIN] = NUMS(2), [PURE_OP_MAX] = NUMS(2), [PURE_OP_CLAMP] = NUMS(3),
[PURE_OP_LOAD] = {0, {0}, T_STATE, READ, A_VALUE, O_ANY},
- [PURE_OP_DT] = {0, {0}, T_NUM, IN_HANDLER, A_NONE, O_ANY},
[PURE_OP_STORE] = {1, {T_STATE}, 0, IN_HANDLER, A_VALUE, O_ANY},
[PURE_OP_JZ] = {1, {T_BOOL}, 0, READ, A_TARGET, O_ANY},
[PURE_OP_JMP] = {0, {0}, 0, READ, A_TARGET, O_ANY},
@@ -82,7 +81,6 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[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},
[PURE_OP_TEXT_LOAD] = {0, {0}, T_TEXT, READ, STATE(TEXT), O_ANY},
- [PURE_OP_TEXT_INPUT] = {0, {0}, T_TEXT, IN_TEXT, A_NONE, O_ANY},
[PURE_OP_NUM_TEXT] = {1, {T_NUM}, T_TEXT, READ, A_FORMAT, O_ANY},
[PURE_OP_CONCAT] = {2, {T_TEXT, T_TEXT}, T_TEXT, READ, A_JOINED, O_ANY},
[PURE_OP_CHARS] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY},
@@ -328,8 +326,9 @@ PURE_COLD static bool name_at(const unsigned char *strings, uint32_t size, uint3
return true;
}
-/* The input whose handler this is, or -1 for frame. */
-static int event_input(uint32_t event) { return event ? (int)((event - 1) / 2) : -1; }
+/* The verifier's arrival: the message input a handler handles or a view's arguments come in, as its number + 1 (0:
+ * none, a function's or a view's without arguments). */
+static int event_input(uint32_t event) { return event ? (int)event - 1 : -1; }
/* The state an argument names must have this type. */
static bool state_of(const PureModule *m, uint32_t state, uint8_t type) {
@@ -506,7 +505,6 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
: !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;
- 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;
if (locals) where |= IN_LOCALS;
@@ -554,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_TEXT) REFUSE("text input outside a text handler");
REFUSE("message operation outside a message handler");
}
uint32_t arg = op->arg;
@@ -1716,36 +1713,38 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
for (uint32_t i = 0; i < out->inputs; ++i, at += INPUT_BYTES) {
PureInputShape *shape = &out->input[i];
shape->kind = at[4];
+ shape->binding = 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]) REFUSE("input flags", i);
- if (shape->kind == PURE_KIND_KEY) {
- 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);
+ /* Arguments bear their view's name (view.args), and a message may bear a path (an aup's hidden one: up.up,
+ * list.point); an output is an identifier. */
+ if (!name_at(text, strings, read32(at), shape->kind == PURE_KIND_OUTPUT ? NAME_IDENTIFIER : NAME_PATH, out->input_names[i]))
+ REFUSE("input name", i);
+ bool arguments = shape->kind == PURE_KIND_ARGUMENTS, keyed = shape->binding == PURE_BIND_PRESS || shape->binding == PURE_BIND_RELEASE;
+ if (read16(at + 6) || shape->binding > PURE_BIND_POINT || (shape->binding && shape->kind != PURE_KIND_MESSAGE))
+ REFUSE("input flags", i);
+ /* What a binding names (027's 4d): a key, a timer's period, a pointing's table (checked with the views). */
+ if (keyed) {
+ if (!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)
- if (out->input[k].kind == PURE_KIND_KEY && !strcmp(out->input_keys[k], out->input_keys[i]))
+ if (out->input[k].binding == shape->binding && !strcmp(out->input_keys[k], out->input_keys[i]))
REFUSE("duplicate key", i);
- continue;
- }
- if (shape->kind == PURE_KIND_TEXT) {
- 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->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->field_start != next_field ||
+ if (shape->binding == PURE_BIND_TIMER && (!(shape->period = read32(at + 8)) || shape->period > PURE_TIMER_LONGEST))
+ REFUSE("timer input", i);
+ shape->view = arguments || shape->binding == PURE_BIND_POINT ? read32(at + 8) : 0;
+ if ((shape->kind != PURE_KIND_MESSAGE && shape->kind != PURE_KIND_OUTPUT && !arguments) ||
+ (read32(at + 8) && !arguments && !shape->binding) || shape->field_start != next_field ||
shape->field_count > out->fields - next_field) REFUSE("message shape", i);
- shape->view = arguments ? read32(at + 8) : 0;
+ /* A binding's fields: a key's and a timer's none, a text's one text, a frame's one number, a pointing's one u32. */
+ static const uint8_t bound_field[] = {[PURE_BIND_TEXT] = PURE_FIELD_TEXT, [PURE_BIND_FRAME] = PURE_FIELD_NUMBER,
+ [PURE_BIND_POINT] = PURE_FIELD_U32};
+ if (shape->binding && shape->field_count != (bound_field[shape->binding] != 0)) REFUSE("binding's fields", i);
+ if (shape->binding && shape->field_count &&
+ read32(fields + (size_t)next_field * FIELD_BYTES + 4) != bound_field[shape->binding]) REFUSE("binding's fields", i);
+ for (uint32_t k = 0; shape->binding == PURE_BIND_FRAME && k < i; ++k)
+ if (out->input[k].binding == PURE_BIND_FRAME) REFUSE("second frame input", i);
for (uint32_t f = 0; f < shape->field_count; ++f) {
const unsigned char *entry = fields + (size_t)(next_field + f) * FIELD_BYTES;
PureField *field = &out->field[next_field + f];
@@ -1866,6 +1865,9 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
* read the table's), one set a view, bearing its name. */
for (uint32_t i = 0; i < out->inputs; ++i) {
const PureInputShape *shape = &out->input[i];
+ /* A pointing points at a table's rows (027's 4d). */
+ if (shape->binding == PURE_BIND_POINT && (shape->view >= out->views || out->view_type[shape->view] != PURE_VIEW_TABLE))
+ REFUSE("pointing's table", i);
if (shape->kind != PURE_KIND_ARGUMENTS) continue;
char path[PURE_MODULE_NAME + 8];
if (shape->view >= out->views || out->view_table[shape->view] || out->view_arguments[shape->view]) REFUSE("arguments", i);
@@ -1878,10 +1880,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
for (uint32_t i = 0; i < out->handlers; ++i, at += HANDLER_BYTES) {
uint32_t event = read32(at);
out->handler[i] = (PureHandler){event, {read32(at + 4), read32(at + 8)}};
- int input = event_input(event);
- if (event > 2u * out->inputs || (input >= 0 && out->input[input].kind != PURE_KIND_KEY && !(event & 1)) ||
- (input >= 0 && (out->input[input].kind == PURE_KIND_OUTPUT || out->input[input].kind == PURE_KIND_ARGUMENTS)))
- REFUSE("handler event", i);
+ if (event >= out->inputs || out->input[event].kind != PURE_KIND_MESSAGE) REFUSE("handler event", i);
}
/* Functions (027's 2c): a block and the counts of its parameters and results, whose types follow every function's. */
for (uint32_t i = 0, item = 0; i < out->functions; ++i, at += FUNCTION_BYTES) {
@@ -1921,7 +1920,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
for (uint32_t i = 0; i < out->views; ++i) {
bad = i;
bool table = out->view_type[i] == PURE_VIEW_TABLE;
- uint32_t event = out->view_arguments[i] ? 2u * out->view_arguments[i] - 1u : 0; /* the arguments' input, as an arrival */
+ uint32_t event = out->view_arguments[i]; /* the arguments' input, as an arrival (its number + 1) */
bool computes = !table && !out->view_table[i]; /* a view outside a table may have a body */
if ((!table || out->view_code[i].length) &&
(why = verify_block(out, out->view_code[i], table ? FILTER : out->view_type[i],
@@ -1960,7 +1959,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
}
for (uint32_t i = 0; i < out->handlers; ++i) {
bad = i;
- if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event, true, 0, 0, -1, &bad))) goto refused;
+ if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event + 1, 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,
@@ -1994,8 +1994,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
}
}
for (uint32_t i = 0; i < out->handlers; ++i) {
- int input = event_input(out->handler[i].event);
- input = input >= 0 && out->input[input].kind == PURE_KIND_MESSAGE ? input : -1;
+ int input = (int)out->handler[i].event;
plan_folds(out, out->handler[i].code, input);
range_code(out, out->handler[i].code, input);
}
@@ -2194,13 +2193,6 @@ int pure_module_view(const PureModule *m, const char *name) {
return -1;
}
-int pure_module_position(const PureModule *m, uint32_t view) {
- if (view >= m->views || m->view_type[view] != PURE_VIEW_NUM || m->view_table[view]) return -1;
- const PureBlock *block = &m->view_code[view];
- const PureOp *code = &m->code[block->start];
- return block->length == 2 && code[0].op == PURE_OP_INDEX_LOAD && code[1].op == PURE_OP_INDEX_NUM ? (int)code[0].arg : -1;
-}
-
int pure_module_text_state(const PureModule *m, uint32_t view) {
if (view >= m->views || m->view_type[view] != PURE_VIEW_TEXT || m->view_table[view]) return -1;
const PureBlock *block = &m->view_code[view];
@@ -3729,7 +3721,6 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
case PURE_OP_BREAK: *next = leave_loop(frame); return STEP_NEXT;
case PURE_OP_CONST: stack[sp++].i64 = op->integer; NEXT; /* the bits: a number's, a bool's or an i64's */
case PURE_OP_LOAD: stack[sp++].i64 = values[arg].i64; NEXT;
- case PURE_OP_DT: stack[sp++].num = ev->dt; NEXT;
case PURE_OP_NOW: stack[sp++].num = app->now; NEXT;
case PURE_OP_FLOOR: top->num = floor(top->num); NEXT;
case PURE_OP_STORE: case PURE_OP_CUR_STORE: values[arg].i64 = stack[--sp].i64; NEXT;
@@ -3770,7 +3761,6 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
stack[sp++].bytes = (PureBytes){FROM_CONSTANT, 0, {0, 0}, m->constant_offset[arg], m->constant_length[arg]};
NEXT;
case PURE_OP_TEXT_LOAD: stack[sp++].bytes = text_of(app, arg); NEXT;
- case PURE_OP_TEXT_INPUT: stack[sp++].bytes = (PureBytes){FROM_PAYLOAD, 0, {0, 0}, 0, app->payload.size}; NEXT;
case PURE_OP_NUM_TEXT: {
char digits[32];
if (arg > 2) { /* in a format */
@@ -4804,7 +4794,6 @@ static void open_burst(PureApp *app) {
if (app->open) return;
const PureModule *m = app->module;
memcpy(app->durable_values, app->values, sizeof(app->values));
- app->durable_ticks = app->ticks;
app->durable_events = app->events;
for (uint32_t i = 0; i < m->states; ++i) {
if (app->texts[i]) {
@@ -4836,7 +4825,6 @@ static void close_burst(PureApp *app, bool keep) {
else {
pure_undo_rollback(&app->undo, 0);
memcpy(app->values, app->durable_values, sizeof(app->values));
- app->ticks = app->durable_ticks;
app->events = app->durable_events;
++app->changes;
}
@@ -5140,57 +5128,10 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) {
memcpy(app->values, pending, sizeof(pending));
++app->events;
++app->changes;
- if (!event) ++app->ticks;
app->delivering.pending = false;
- return event && !app->deferred ? pure_app_save(app) : PURE_OK;
-}
-
-PureResult pure_app_frame(PureApp *app) {
- pure_app_finish(app);
- Event ev = {.app = app, .arena = &app->arena, .dt = 1.0 / 60.0};
- return dispatch(app, 0, &ev);
-}
-
-PureResult pure_app_tick(PureApp *app, uint32_t input) {
- if (input >= app->module->inputs || app->module->input[input].kind != PURE_KIND_TIMER) return PURE_INVALID;
- pure_app_finish(app);
- Event ev = {.app = app, .arena = &app->arena};
- return dispatch(app, 1 + 2 * input, &ev);
-}
-
-PureResult pure_app_input(PureApp *app, uint32_t input, PureEdge edge) {
- if (input >= app->module->inputs || app->module->input[input].kind != PURE_KIND_KEY ||
- (edge != PURE_EDGE_DOWN && edge != PURE_EDGE_UP)) return PURE_INVALID;
- pure_app_finish(app);
- Event ev = {.app = app, .arena = &app->arena};
- return dispatch(app, 1 + 2 * input + (edge == PURE_EDGE_UP), &ev);
-}
-
-PureResult pure_app_point(PureApp *app, uint32_t state, size_t position) {
- const PureModule *m = app->module;
- pure_app_finish(app);
- if (state >= m->states || m->state_type[state] != PURE_STATE_INDEX) return PURE_INVALID;
- if (position >= pure_list_count(app->lists[m->state_ref[state]])) return PURE_NOT_FOUND;
- open_burst(app); /* what a failed save returns to */
- app->fault = PURE_FAULT_NONE;
- app->fault_pc = UINT32_MAX;
- app->values[state].num = (double)position;
- ++app->events;
- ++app->changes;
return app->deferred ? PURE_OK : pure_app_save(app);
}
-PureResult pure_app_text(PureApp *app, uint32_t input, PureSpan text) {
- pure_app_finish(app);
- if (input >= app->module->inputs || app->module->input[input].kind != PURE_KIND_TEXT ||
- (text.size && !text.data) || !pure_utf8_valid(text)) return PURE_INVALID;
- app->payload = text;
- Event ev = {.app = app, .arena = &app->arena};
- PureResult result = dispatch(app, 1 + 2 * input, &ev);
- app->payload = (PureSpan){0};
- return result;
-}
-
static PureResult keep_reply(PureApp *app) {
size_t length = (size_t)app->reply.length, start = app->arena.used;
unsigned char *copy = malloc(length ? length : 1);
@@ -5287,7 +5228,7 @@ static PureResult message(PureApp *app, uint32_t input, PureSpan payload, const
}
app->payload = payload;
Event ev = {.app = app, .shape = shape, .arena = &app->arena, .nested = nested, .spans = spans};
- PureResult result = dispatch(app, 1 + 2 * input, &ev);
+ PureResult result = dispatch(app, input, &ev);
if (result != PURE_OK) pure_app_finish(app);
return result;
}
diff --git a/core/module.h b/core/module.h
index 16c2a70..7cf90b9 100644
--- a/core/module.h
+++ b/core/module.h
@@ -23,13 +23,13 @@ enum {
};
typedef enum {
- PURE_OP_CONST, PURE_OP_LOAD, PURE_OP_DT, PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL,
+ PURE_OP_CONST, PURE_OP_LOAD, PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL,
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_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_TEXT_CONST, PURE_OP_TEXT_LOAD, PURE_OP_NUM_TEXT,
PURE_OP_CONCAT, PURE_OP_CHARS, PURE_OP_LINES, PURE_OP_TEXT_BYTES,
PURE_OP_ANCHOR_LOAD, PURE_OP_ANCHOR_START, PURE_OP_ANCHOR_END, PURE_OP_ANCHOR_MOVE,
PURE_OP_ANCHOR_STORE, PURE_OP_TEXT_INSERT, PURE_OP_TEXT_REMOVE, PURE_OP_TEXT_LINE,
@@ -206,15 +206,23 @@ enum { PURE_LOOP_INTEGER = 256, PURE_MODULE_NESTING = 8 };
* comparisons (LT LE GT GE EQ NE); texts order by their bytes. */
typedef enum { PURE_LESS, PURE_LESS_EQUAL, PURE_GREATER, PURE_GREATER_EQUAL, PURE_EQUAL, PURE_NOT_EQUAL } PureRelation;
-/* A timer ticks every `period` milliseconds while a host runs the app. An
- * output is a message the app sends (SEND, in a handler; it has fields as a
- * message has and no handler): what receives it is the host's wiring.
- * Arguments are the fields a view takes (a view with arguments, its
- * columns and its filter read them as a message handler reads its message);
- * such an input has no handler and names its view, whose name it bears
- * with .arguments after it. */
-typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2, PURE_KIND_TEXT = 3, PURE_KIND_TIMER = 4,
- PURE_KIND_OUTPUT = 5, PURE_KIND_ARGUMENTS = 6 } PureInputKind;
+/* An input is a message (027's 4d: the only thing an app takes), an output
+ * or a view's arguments. An output is a message the app sends (SEND, in a
+ * handler; it has fields as a message has and no handler): what receives it
+ * is the host's wiring. Arguments are the fields a view takes (a view with
+ * arguments, its columns and its filter read them as a message handler reads
+ * its message); such an input has no handler and names its view, whose name
+ * it bears with .args after it. */
+typedef enum { PURE_KIND_MESSAGE = 1, PURE_KIND_OUTPUT = 2, PURE_KIND_ARGUMENTS = 3 } PureInputKind;
+/* What makes a message, for its hosts and surfaces (027's 4d): nothing said
+ * (a sender's), a key pressed or released (the key's name: no fields), text
+ * that came in (one text field), a timer's tick every period milliseconds
+ * (no fields: the time is the event's now), a frame of the screen (one
+ * number field, the seconds since the last) or a pointing at a row of a
+ * table view (one u32 field, the row). The runtime reads none of it but the
+ * fields it says a message has. */
+typedef enum { PURE_BIND_NONE, PURE_BIND_PRESS, PURE_BIND_RELEASE, PURE_BIND_TEXT, PURE_BIND_TIMER, PURE_BIND_FRAME,
+ PURE_BIND_POINT } PureBinding;
enum { PURE_TIMER_LONGEST = 86400000, PURE_SOURCE = 64 };
/* The largest number a delivery may have: it is kept as a binary64. */
#define PURE_SEQ_MOST (UINT64_C(1) << 53)
@@ -283,7 +291,6 @@ enum { PURE_STATE_TRANSIENT = 256 };
* empty (a reader's own: readers of one snapshot compute apart). */
typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2, PURE_VIEW_BOOL = 3, PURE_VIEW_I64 = 4 } PureViewType;
typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit;
-typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge;
/* EXISTS: an item with that key is already in the keyed list (the event
* reports PURE_CONFLICT). MEMORY: the event would leave the states holding
* more than the app's budget (pure_app_memory; it reports PURE_BUDGET).
@@ -307,7 +314,7 @@ const char *pure_fault_name(PureFault);
* PureRange + 1). */
typedef struct { union { double value; int64_t integer; }; uint32_t arg; uint8_t op, plan, shape, range; } PureOp;
typedef struct { uint32_t start, length; } PureBlock;
-typedef struct { uint32_t event; PureBlock code; } PureHandler;
+typedef struct { uint32_t event; PureBlock code; } PureHandler; /* event: the message input it handles (027's 4d) */
/* A function's block and its parameters' and results' types, from first among the module's signature items: a record
* field's type (PureRecordType: a list's with its record, bytes as PURE_RECORD_BYTES). Set at load: the event's lists,
* registers and held values each call has its own of (a bit each), the lists not an event's it changes (or a function
@@ -321,11 +328,11 @@ typedef struct {
} PureFunction;
typedef struct { char name[PURE_MODULE_NAME]; uint8_t type; uint16_t rows; uint32_t offset; } PureField;
typedef struct {
- uint8_t kind;
+ uint8_t kind, binding; /* PureInputKind, PureBinding (a key's name in input_keys) */
uint16_t field_start, field_count;
uint32_t prefix; /* fixed payload bytes before the variable fields */
uint32_t period; /* a timer's, in milliseconds */
- uint32_t view; /* arguments: the view they are for */
+ uint32_t view; /* arguments: the view they are for; a pointing's: its table */
} PureInputShape;
/* A record type: fields numbered from first, each a number or text. */
@@ -454,9 +461,6 @@ bool pure_module_takes(const PureModule *, uint32_t input, const unsigned char *
* when payload is not laid out with them. */
PureResult pure_module_recast(const PureModule *, uint32_t input, const unsigned char *fields, size_t size,
PureSpan payload, unsigned char **out, size_t *out_size);
-/* The index state a number view shows as its position (a view that is just
- * `at.position`), or -1: what a host moves with pure_app_point. */
-int pure_module_position(const PureModule *, uint32_t view);
/* The text state a text view shows as it is (a view that is just the
* state), or -1: a host may lend its stored extents instead of copying. */
int pure_module_text_state(const PureModule *, uint32_t view);
@@ -522,7 +526,7 @@ typedef struct {
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;
+ uint64_t durable_events;
PureFault fault;
uint32_t fault_pc;
size_t memory; /* the budget of what the states hold (0: none): pure_app_memory */
@@ -533,7 +537,7 @@ typedef struct {
PureFault view_fault; /* why the last view evaluated had no value (pure_app_view_fault) */
char why[128]; /* why the last start or replace was refused, when it says (else empty; a failed start keeps it) */
uint32_t counted; /* the states it counts, a bit each: lists (not a message's rows) and texts */
- uint64_t ticks, events;
+ uint64_t events;
/* The time events see as now, in milliseconds since 1970 (UTC). The host
* sets it, so a script can replay any time; views never see it. */
double now;
@@ -600,19 +604,10 @@ PureResult pure_app_save(PureApp *);
/* Defer saving to pure_app_save (false: save after each event). Needs a data
* host that saves together (PureData.save). */
bool pure_app_defer(PureApp *, bool deferred);
-/* Leaves unsaved frame changes unsaved: call pure_app_save first to keep them. */
+/* Leaves unsaved changes unsaved: call pure_app_save first to keep them. */
void pure_app_stop(PureApp *);
-PureResult pure_app_frame(PureApp *);
-/* A timer input's tick: an event saved like a key's. */
-PureResult pure_app_tick(PureApp *, uint32_t input);
-PureResult pure_app_input(PureApp *, uint32_t input, PureEdge);
-/* Point an index state at the item at position (0 first), as a user pointing
- * at a row: an event with no handler, saved like a key event. NOT_FOUND past
- * the last item, INVALID for a state that is not an index. */
-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. payload stays alive until pure_app_finish. */
+/* Deliver a message (a key's, a text's, a tick's or a frame's too, as its binding says it is laid out). 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
diff --git a/core/test-module.c b/core/test-module.c
index 3c221e4..a27c0aa 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -35,12 +35,15 @@ int pure_store_test_fault(PureStoreStage stage) {
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. */
+/* kind 0 is a key input, TEXT_INPUT a text input, TIMER_INPUT a timer; otherwise PURE_KIND_MESSAGE with fields.
+ * Arguments name their view by its number in key. A module has messages bound so (027's 4d): expand writes them. */
typedef struct { const char *name, *key; int kind; const F *fields; size_t nfields; } I;
+/* POINT_INPUT: a pointing message (one u32 field), its table the view numbered key. DT and TI (250, 251): dt and a
+ * text input's text, which a module reads as fields. */
+enum { TEXT_INPUT = 100, TIMER_INPUT = 101, POINT_INPUT = 102, TEST_DT = 250, TEST_TEXT_IN = 251 };
#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, NULL, period}
+#define TIMER(name, period) {name, NULL, TIMER_INPUT, 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)
@@ -113,7 +116,74 @@ static uint8_t module_op(uint8_t op) {
}
}
+/* A test's inputs, handlers and code as a module has them (027's 4d): a key input a message its press makes, one more
+ * its release makes for a key up's handler (event 2 + 2i), a frame's message (one number field) for a frame's handler
+ * (event 0), a text input a message of one text field, a timer a message its ticks make; a handler's event its
+ * message's number; dt (DT, 250) the frame's field in its handler and 0 elsewhere, a text input's text (TI, 251) its
+ * field. */
+static I expanded_in[64];
+static struct { uint8_t binding; uint32_t period; } expanded_bound[64];
+static H expanded_h[64];
+static O expanded_o[8192];
+static Spec expand(Spec spec) {
+ static const F text_field[] = {{"text", PURE_FIELD_TEXT}}, dt_field[] = {{"dt", PURE_FIELD_NUMBER}};
+ static char release_names[64][PURE_MODULE_NAME];
+ static const F position_field[] = {{"position", PURE_FIELD_U32}};
+ size_t ni = 0;
+ for (size_t i = 0; i < spec.ni; ++i) {
+ I in = spec.in[i];
+ uint8_t binding = 0;
+ uint32_t period = 0;
+ if (!in.kind) in = (I){in.name, in.key, PURE_KIND_MESSAGE, NULL, 0}, binding = PURE_BIND_PRESS;
+ else if (in.kind == TEXT_INPUT) in = (I){in.name, NULL, PURE_KIND_MESSAGE, text_field, 1}, binding = PURE_BIND_TEXT;
+ else if (in.kind == TIMER_INPUT)
+ period = (uint32_t)in.nfields, in = (I){in.name, NULL, PURE_KIND_MESSAGE, NULL, 0}, binding = PURE_BIND_TIMER;
+ else if (in.kind == POINT_INPUT) in = (I){in.name, in.key, PURE_KIND_MESSAGE, position_field, 1}, binding = PURE_BIND_POINT;
+ expanded_bound[ni].binding = binding, expanded_bound[ni].period = period;
+ expanded_in[ni++] = in;
+ }
+ int release[64], frame = -1;
+ for (size_t i = 0; i < spec.ni; ++i) release[i] = -1;
+ for (size_t h = 0; h < spec.nh; ++h) {
+ uint32_t e = spec.h[h].event;
+ if (!e && frame < 0) {
+ frame = (int)ni;
+ expanded_bound[ni].binding = PURE_BIND_FRAME, expanded_bound[ni].period = 0;
+ expanded_in[ni++] = (I){"frame", NULL, PURE_KIND_MESSAGE, dt_field, 1};
+ } else if (e && !(e & 1) && (e - 2) / 2 < spec.ni && release[(e - 2) / 2] < 0 &&
+ expanded_bound[(e - 2) / 2].binding == PURE_BIND_PRESS) {
+ uint32_t of = (e - 2) / 2;
+ snprintf(release_names[of], PURE_MODULE_NAME, "%s.up", spec.in[of].name);
+ release[of] = (int)ni;
+ expanded_bound[ni].binding = PURE_BIND_RELEASE, expanded_bound[ni].period = 0;
+ expanded_in[ni++] = (I){release_names[of], spec.in[of].key, PURE_KIND_MESSAGE, NULL, 0};
+ }
+ }
+ CHECK(ni <= 64 && spec.nh <= 64 && spec.no <= 8192);
+ for (size_t h = 0; h < spec.nh; ++h) {
+ uint32_t e = spec.h[h].event;
+ expanded_h[h] = spec.h[h];
+ /* an event past the inputs, or a release of what is not a key, past them all (a test of a refusal) */
+ int up = !(e & 1) && e && (e - 2) / 2 < spec.ni ? release[(e - 2) / 2] : -1;
+ expanded_h[h].event = !e ? (uint32_t)frame : e & 1 ? (e - 1) / 2 : up >= 0 ? (uint32_t)up : 1000 + e;
+ }
+ for (size_t k = 0; k < spec.no; ++k) {
+ expanded_o[k] = spec.o[k];
+ if (spec.o[k].op == TEST_TEXT_IN) expanded_o[k] = (O){PURE_OP_FIELD, 0, 0};
+ if (spec.o[k].op != TEST_DT) continue;
+ bool in_frame = false;
+ for (size_t h = 0; h < spec.nh; ++h)
+ in_frame = in_frame || (!spec.h[h].event && k >= spec.h[h].start && k < spec.h[h].start + spec.h[h].length);
+ expanded_o[k] = in_frame ? (O){PURE_OP_FIELD, 0, 0} : (O){PURE_OP_CONST, 0, 0};
+ }
+ spec.in = expanded_in, spec.ni = ni;
+ spec.h = expanded_h;
+ spec.o = expanded_o;
+ return spec;
+}
+
static size_t build(unsigned char *out, Spec spec) {
+ spec = expand(spec);
unsigned char strings[4096], blob[4096];
size_t used = 0, blob_used = 0;
const char *text[512];
@@ -123,9 +193,8 @@ static size_t build(unsigned char *out, Spec spec) {
for (size_t i = 0; i < spec.nt; ++i) text[names++] = spec.t[i].name;
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;
- for (size_t f = 0; spec.in[i].kind != PURE_KIND_TIMER && f < spec.in[i].nfields; ++f)
- text[names++] = spec.in[i].fields[f].name;
+ if (expanded_bound[i].binding == PURE_BIND_PRESS || expanded_bound[i].binding == PURE_BIND_RELEASE) text[names++] = spec.in[i].key;
+ for (size_t f = 0; f < spec.in[i].nfields; ++f) text[names++] = spec.in[i].fields[f].name;
}
for (size_t i = 0; i < spec.nv; ++i) text[names++] = spec.v[i].name;
for (size_t i = 0; i < spec.ntv; ++i) text[names++] = spec.tv[i].name;
@@ -144,7 +213,7 @@ static size_t build(unsigned char *out, Spec spec) {
used += length;
}
size_t fields = 0;
- 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.ni; ++i) fields += 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;
CHECK(!spec.nc); /* (collections are gone: 027's 4b) */
@@ -178,21 +247,21 @@ static size_t build(unsigned char *out, Spec spec) {
size_t nf = 0;
for (size_t i = 0; i < spec.ni; ++i) {
const I *in = &spec.in[i];
+ uint8_t binding = expanded_bound[i].binding;
+ bool keyed = binding == PURE_BIND_PRESS || binding == PURE_BIND_RELEASE;
p = put32(p, offset[n++]);
- *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++]);
- uint32_t first = in->kind == PURE_KIND_MESSAGE || in->kind == PURE_KIND_OUTPUT || in->kind == PURE_KIND_ARGUMENTS
- ? field_start : 0; /* only messages, outputs and arguments have fields */
- *p++ = (unsigned char)first; *p++ = (unsigned char)(first >> 8);
- *p++ = (unsigned char)(timer ? 0 : in->nfields); *p++ = 0;
- for (size_t f = 0; !timer && f < in->nfields; ++f) field_offsets[nf++] = offset[n++];
- field_start += timer ? 0 : (uint32_t)in->nfields;
+ *p++ = (unsigned char)in->kind; *p++ = binding; *p++ = 0; *p++ = 0;
+ p = put32(p, binding == PURE_BIND_TIMER ? expanded_bound[i].period :
+ in->kind == PURE_KIND_ARGUMENTS || binding == PURE_BIND_POINT ? (uint32_t)strtoul(in->key, NULL, 10) :
+ keyed ? offset[n++] : 0);
+ *p++ = (unsigned char)field_start; *p++ = (unsigned char)(field_start >> 8);
+ *p++ = (unsigned char)in->nfields; *p++ = 0;
+ for (size_t f = 0; f < in->nfields; ++f) field_offsets[nf++] = offset[n++];
+ field_start += (uint32_t)in->nfields;
}
nf = 0;
for (size_t i = 0; i < spec.ni; ++i)
- for (size_t f = 0; spec.in[i].kind != PURE_KIND_TIMER && f < spec.in[i].nfields; ++f) {
+ for (size_t f = 0; f < spec.in[i].nfields; ++f) {
p = put32(p, field_offsets[nf++]);
p = put32(p, spec.in[i].fields[f].type);
}
@@ -254,6 +323,45 @@ static size_t build(unsigned char *out, Spec spec) {
}
static PureModule module_a, module_b;
+
+/* A device's events as a module takes them (027's 4d): a key's press and release, a frame, a tick, text, each the
+ * message bound so (a release or a frame no message takes is no event); one for an input not bound so is INVALID. */
+static PureResult press(PureApp *app, uint32_t input) {
+ if (input >= app->module->inputs || app->module->input[input].binding != PURE_BIND_PRESS) return PURE_INVALID;
+ return pure_app_message(app, input, (PureSpan){0});
+}
+static PureResult release(PureApp *app, uint32_t input) {
+ const PureModule *m = app->module;
+ if (input >= m->inputs || m->input[input].binding != PURE_BIND_PRESS) return PURE_INVALID;
+ for (uint32_t i = 0; i < m->inputs; ++i)
+ if (m->input[i].binding == PURE_BIND_RELEASE && !strcmp(m->input_keys[i], m->input_keys[input]))
+ return pure_app_message(app, i, (PureSpan){0});
+ return PURE_OK;
+}
+static PureResult frame(PureApp *app) {
+ static const double dt = 1.0 / 60.0;
+ for (uint32_t i = 0; i < app->module->inputs; ++i)
+ if (app->module->input[i].binding == PURE_BIND_FRAME) return pure_app_message(app, i, (PureSpan){(const unsigned char *)&dt, 8});
+ return PURE_OK;
+}
+static PureResult tick(PureApp *app, uint32_t input) {
+ if (input >= app->module->inputs || app->module->input[input].binding != PURE_BIND_TIMER) return PURE_INVALID;
+ return pure_app_message(app, input, (PureSpan){0});
+}
+/* A test's index put on an item, as a host's pointing did before 027's 4d (now a message an app takes: pointing), and
+ * saved unless deferred. */
+static PureResult place(PureApp *app, uint32_t state, size_t position) {
+ const PureModule *m = app->module;
+ if (state >= m->states || m->state_type[state] != PURE_STATE_INDEX) return PURE_INVALID;
+ if (position >= pure_list_count(app->lists[m->state_ref[state]])) return PURE_NOT_FOUND;
+ app->values[state].num = (double)position;
+ ++app->changes;
+ return app->deferred ? PURE_OK : pure_app_save(app);
+}
+static PureResult type_in(PureApp *app, uint32_t input, PureSpan text) {
+ if (input >= app->module->inputs || app->module->input[input].binding != PURE_BIND_TEXT) return PURE_INVALID;
+ return pure_app_message(app, input, text);
+}
static char error[128];
static PureResult load(Spec spec, PureModule *out) {
@@ -272,7 +380,7 @@ static PureResult load_functions(Spec spec, const FN *fns, size_t count, PureMod
#define LEN(a) (sizeof(a) / sizeof(*(a)))
#define SPEC(s, in, v, h, o) ((Spec){s, LEN(s), in, LEN(in), v, LEN(v), h, LEN(h), o, LEN(o), NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0})
-enum { C = PURE_OP_CONST, L = PURE_OP_LOAD, DT = PURE_OP_DT, ADD = PURE_OP_ADD,
+enum { C = PURE_OP_CONST, L = PURE_OP_LOAD, DT = TEST_DT, ADD = PURE_OP_ADD,
MUL = PURE_OP_MUL, DIV = PURE_OP_DIV, GT = PURE_OP_GT, MIN = PURE_OP_MIN,
MAX = PURE_OP_MAX, CL = PURE_OP_CLAMP, ST = PURE_OP_STORE, JZ = PURE_OP_JZ,
JMP = PURE_OP_JMP, NEG = PURE_OP_NEG };
@@ -306,16 +414,16 @@ static void behaviour(void) {
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
double value;
CHECK(pure_app_view(&app, 1, &value) && value == 40);
- for (int i = 0; i < 25; ++i) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ for (int i = 0; i < 25; ++i) CHECK(press(&app, 0) == PURE_OK);
CHECK(app.values[0].num == 25 && pure_app_view(&app, 1, &value) && value == 200);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_UP) == PURE_OK && app.events == 26);
+ CHECK(release(&app, 0) == PURE_OK && app.events == 25);
/* if / else with forward JZ and JMP */
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[1].num == 4);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[1].num == 5);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[1].num == 6);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[1].num == 0);
- CHECK(pure_app_frame(&app) == PURE_OK && app.values[1].num == 1.0 / 60.0 && app.ticks == 1);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_INVALID);
+ CHECK(press(&app, 1) == PURE_OK && app.values[1].num == 4);
+ CHECK(press(&app, 1) == PURE_OK && app.values[1].num == 5);
+ CHECK(press(&app, 1) == PURE_OK && app.values[1].num == 6);
+ CHECK(press(&app, 1) == PURE_OK && app.values[1].num == 0);
+ CHECK(frame(&app) == PURE_OK && app.values[1].num == 1.0 / 60.0);
+ CHECK(press(&app, 2) == PURE_INVALID);
CHECK(pure_module_input(&module_a, "move") == 1 && pure_module_view(&module_a, "width") == 1 &&
pure_module_state(&module_a, "missing") == -1);
@@ -352,7 +460,7 @@ static void faults(void) {
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
const PureFault expected[] = {PURE_FAULT_DIVIDE, PURE_FAULT_NON_FINITE, PURE_FAULT_CLAMP};
for (uint32_t i = 0; i < 3; ++i) {
- CHECK(pure_app_input(&app, i, PURE_EDGE_DOWN) == PURE_INVALID && app.fault == expected[i]);
+ CHECK(press(&app, i) == PURE_INVALID && app.fault == expected[i]);
CHECK(app.values[0].num == 1 && app.values[1].num == 2 && app.events == 0);
}
/* A later handler of the same event does not run after a fault. */
@@ -376,11 +484,11 @@ static void rejections(void) {
static const I dup_key[] = {KEY("k", "Space"), KEY("j", "Space")};
static const V clash[] = {{"a", 0, 1}};
static const V v_store[] = {{"v", 0, 2}};
- static const V v_dt[] = {{"v", 2, 1}};
+ static const V v_field[] = {{"v", 2, 1}};
static const V v_empty[] = {{"v", 0, 0}};
static const H none[1];
static const O load_a[] = {{L, 0, 0}};
- static const O store[] = {{C, 0, 1}, {ST, 0, 0}, {DT, 0, 0}};
+ static const O store[] = {{C, 0, 1}, {ST, 0, 0}, {PURE_OP_FIELD, 0, 0}};
static const H h0[] = {{0, 0, 2}};
static const H h_bad_event[] = {{3, 0, 2}};
static const O back[] = {{C, 1, 1}, {JZ, 0, 0}};
@@ -409,7 +517,7 @@ static void rejections(void) {
{"duplicate key", {s1, 1, dup_key, 2, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
{"duplicate name", {s1, 1, i1, 0, clash, 1, none, 0, load_a, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
{"view with an effect", {s1, 1, i1, 0, v_store, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
- {"view with an effect", {s1, 1, i1, 0, v_dt, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
+ {"view with an effect", {s1, 1, i1, 0, v_field, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
{"empty view", {s1, 1, i1, 0, v_empty, 1, none, 0, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
{"handler event", {s1, 1, i1, 1, clash, 0, h_bad_event, 1, store, 3, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
{"branch not forward", {s1, 1, i1, 0, clash, 0, h_two, 1, back, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, PURE_INVALID},
@@ -467,8 +575,8 @@ static void mutations(void) {
PureApp app;
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
for (unsigned e = 0; e < 16; ++e) {
- if (e % 3 == 0) pure_app_frame(&app);
- else pure_app_input(&app, e % 4, e & 1 ? PURE_EDGE_UP : PURE_EDGE_DOWN);
+ if (e % 3 == 0) frame(&app);
+ else (e & 1 ? release(&app, e % 4) : press(&app, e % 4));
for (uint32_t v = 0; v < module_a.views; ++v) { double value; pure_app_view(&app, v, &value); }
for (uint32_t s = 0; s < module_a.states; ++s) CHECK(isfinite(app.values[s].num));
}
@@ -512,7 +620,7 @@ enum { FIELD = PURE_OP_FIELD, LEN_ = PURE_OP_LEN, REPLY = PURE_OP_REPLY, REQ = P
/* 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. */
+ * require changes nothing, and a key's press, a message with no fields, is one no handler takes here. */
static void messages(void) {
static const F edit_fields[] = {{"start", 1}, {"remove", 1}, {"insert", 2}};
static const I inputs[] = {
@@ -545,7 +653,7 @@ static void messages(void) {
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);
+ CHECK(pure_app_message(&app, 3, (PureSpan){0}) == PURE_OK && app.values[0].num == 2 && app.events == 4);
pure_app_stop(&app);
/* Verifier rules for typed operations. */
@@ -553,7 +661,7 @@ static void messages(void) {
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"));
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "no such state, field")); /* a press holds no fields */
static const O bytes_state[] = {{FIELD, 2, 0}, {ST, 0, 0}};
static const H edit_handler[] = {{3, 0, 2}};
bad.o = bytes_state; bad.no = 2; bad.h = edit_handler;
@@ -610,8 +718,8 @@ static void message_mutations(void) {
size_t reply = 0;
CHECK(pure_app_reply(&app, count_bytes, &reply) == PURE_OK && reply <= 256);
}
- } else pure_app_input(&app, input, e & 1 ? PURE_EDGE_UP : PURE_EDGE_DOWN);
- pure_app_frame(&app);
+ } else (e & 1 ? release(&app, input) : press(&app, input));
+ frame(&app);
for (uint32_t s = 0; s < module_b.states; ++s) CHECK(isfinite(app.values[s].num));
}
pure_app_stop(&app);
@@ -663,7 +771,7 @@ static double number_view(PureApp *app, uint32_t view) {
return value;
}
-enum { CUL = PURE_OP_CUR_LOAD, CS = PURE_OP_CUR_STORE, CM = PURE_OP_CUR_MOVE, TI = PURE_OP_TEXT_INPUT,
+enum { CUL = PURE_OP_CUR_LOAD, CS = PURE_OP_CUR_STORE, CM = PURE_OP_CUR_MOVE, TI = TEST_TEXT_IN,
TINS = PURE_OP_TEXT_INSERT, TREM = PURE_OP_TEXT_REMOVE, TL = PURE_OP_TEXT_LOAD, TK = PURE_OP_TEXT_CONST,
CHR = PURE_OP_CHARS, NT = PURE_OP_NUM_TEXT, CAT = PURE_OP_CONCAT, TLN = PURE_OP_TEXT_LINE,
CC = PURE_OP_CUR_COUNT, CST = PURE_OP_CUR_START };
@@ -678,7 +786,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, NULL, 0}, KEY("left", "ArrowLeft"), KEY("right", "ArrowRight"),
+ {"typed", NULL, TEXT_INPUT, 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[] = {
@@ -709,38 +817,38 @@ static void texts(void) {
PureApp app;
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
CHECK(!strcmp(state_text(&app, 1), "\xea\xb0\x80" "a\nbc") && app.values[2].num == 0);
- for (int i = 0; i < 6; ++i) CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK);
+ for (int i = 0; i < 6; ++i) CHECK(press(&app, 2) == PURE_OK);
CHECK(app.values[2].num == 7); /* stops at the end */
CHECK(number_view(&app, 0) == 5 && number_view(&app, 2) == 1 && !strcmp(view_text(&app, 3), "bc"));
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 4); /* up keeps column 2 */
+ CHECK(press(&app, 3) == PURE_OK && app.values[2].num == 4); /* up keeps column 2 */
const char *han = "\xed\x95\x9c";
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)han, 3}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)han, 3}) == PURE_OK);
CHECK(!strcmp(state_text(&app, 1), "\xea\xb0\x80" "a\xed\x95\x9c\nbc") && app.values[2].num == 7 && app.values[0].num == 1);
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 4); /* back over 3 bytes */
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 3);
+ CHECK(press(&app, 5) == PURE_OK && app.values[2].num == 4); /* back over 3 bytes */
+ CHECK(press(&app, 5) == PURE_OK && app.values[2].num == 3);
CHECK(!strcmp(state_text(&app, 1), "\xea\xb0\x80\nbc"));
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 0);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 0); /* stops at the start */
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 0); /* nothing before */
- CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 4); /* down to column 0 */
+ CHECK(press(&app, 1) == PURE_OK && app.values[2].num == 0);
+ CHECK(press(&app, 1) == PURE_OK && app.values[2].num == 0); /* stops at the start */
+ CHECK(press(&app, 5) == PURE_OK && app.values[2].num == 0); /* nothing before */
+ CHECK(press(&app, 4) == PURE_OK && app.values[2].num == 4); /* down to column 0 */
/* A failing event restores the text, the cursor and the states. */
- CHECK(pure_app_input(&app, 7, PURE_EDGE_DOWN) == PURE_INVALID && app.fault == PURE_FAULT_DIVIDE);
+ CHECK(press(&app, 7) == PURE_INVALID && app.fault == PURE_FAULT_DIVIDE);
CHECK(!strcmp(state_text(&app, 1), "\xea\xb0\x80\nbc") && app.values[2].num == 4 && app.values[0].num == 1);
CHECK(!strcmp(view_text(&app, 4), "\xeb\xa9\x94\xeb\xaa\xa8 \xc2\xb7 4\xec\x9e\x90"));
CHECK(!strcmp(view_text(&app, 5), "0.3333333333333333"));
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"\xff", 1}) == PURE_INVALID);
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"\xed\xa0\x80", 3}) == PURE_INVALID); /* surrogate */
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"\xf0\x9f\x98\x80", 4}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"\xff", 1}) == PURE_INVALID);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"\xed\xa0\x80", 3}) == PURE_INVALID); /* surrogate */
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"\xf0\x9f\x98\x80", 4}) == PURE_OK);
CHECK(app.values[2].num == 8 && number_view(&app, 1) == 3); /* offset in characters */
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 4); /* back over the emoji */
- CHECK(pure_app_input(&app, 6, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 0);
+ CHECK(press(&app, 1) == PURE_OK && app.values[2].num == 4); /* back over the emoji */
+ CHECK(press(&app, 6) == PURE_OK && app.values[2].num == 0);
/* Replacement keeps the text and its cursor by name. */
static const S fresh[] = {{"fresh", 7}};
static const T kept[] = {{"note", 1, 0}, {"at", 2, 1}};
static const char *const other[] = {"ignored"};
Spec next = {fresh, 1, inputs, 0, number_views, 0, handlers, 0, code, 0, NULL, 0, kept, 2, other, 1, text_views, 0, NULL, 0, NULL, 0};
CHECK(load(next, &module_b) == PURE_OK);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 3);
+ CHECK(press(&app, 2) == PURE_OK && app.values[2].num == 3);
CHECK(pure_app_replace(&app, &module_b) == PURE_OK);
CHECK(app.values[0].num == 7 && app.values[2].num == 3 && !strcmp(state_text(&app, 1), "\xea\xb0\x80\n\xf0\x9f\x98\x80" "bc"));
pure_app_stop(&app);
@@ -753,7 +861,7 @@ static void texts(void) {
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "cursor of another text"));
static const O input_in_frame[] = {{CUL, 3, 0}, {TI, 0, 0}, {TINS, 1, 0}};
bad.o = input_in_frame;
- CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "text input outside"));
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand type")); /* a frame's field is its dt */
static const O store_text[] = {{TK, 0, 0}, {ST, 0, 0}};
static const H frame2[] = {{0, 0, 2}};
bad.o = store_text; bad.no = 2; bad.h = frame2;
@@ -798,11 +906,11 @@ static void text_mutations(void) {
if (pure_app_start(&app, &module_b, (PureData){0}) != PURE_OK) continue;
for (unsigned e = 0; e < 16; ++e) {
uint32_t input = next_random(&state) % 8;
- if (module_b.input[input].kind == PURE_KIND_TEXT) {
+ if (module_b.input[input].binding == PURE_BIND_TEXT) {
const char *word = words[next_random(&state) % LEN(words)];
- pure_app_text(&app, input, (PureSpan){(const unsigned char *)word, strlen(word)});
- } else if (module_b.input[input].kind == PURE_KIND_KEY) pure_app_input(&app, input, PURE_EDGE_DOWN);
- else pure_app_frame(&app);
+ type_in(&app, input, (PureSpan){(const unsigned char *)word, strlen(word)});
+ } else if (module_b.input[input].binding == PURE_BIND_PRESS) press(&app, input);
+ else frame(&app);
for (uint32_t v = 0; v < module_b.views; ++v) {
double value;
if (module_b.view_type[v] == PURE_VIEW_TEXT) pure_app_view_text(&app, v, ignore_span, NULL);
@@ -1990,7 +2098,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, NULL, 0}, KEY("ins", "KeyI"), KEY("del", "Delete"),
+ {"typed", NULL, TEXT_INPUT, 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[] = {
@@ -2022,34 +2130,34 @@ static void lists(void) {
CHECK(number_view(&app, 0) == 0 && number_view(&app, 1) == 0 && !pure_app_view(&app, 2, &value));
CHECK(pure_app_view_text(&app, 3, text_span, &(Text){{0}, 0}) == PURE_NOT_FOUND); /* no item yet */
CHECK(app.fault == PURE_FAULT_NONE); /* views leave faults alone */
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"a", 1}) == PURE_OK);
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"b", 1}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"a", 1}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"b", 1}) == PURE_OK);
CHECK(pure_list_count(app.lists[1]) == 2 && app.values[2].num == 1 && app.values[0].num == 2);
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 0);
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 0); /* stops at the first */
+ CHECK(press(&app, 3) == PURE_OK && app.values[2].num == 0);
+ CHECK(press(&app, 3) == PURE_OK && app.values[2].num == 0); /* stops at the first */
/* Inserting before the current item: the index follows it. */
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 1);
+ CHECK(press(&app, 1) == PURE_OK && app.values[2].num == 1);
CHECK(!strcmp(item_title(&app, 1, 0), "new") && !strcmp(view_text(&app, 3), "a"));
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_OK && number_view(&app, 2) == 1);
- CHECK(pure_app_input(&app, 6, PURE_EDGE_DOWN) == PURE_OK && !strcmp(view_text(&app, 3), "ax"));
- CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 2);
- CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 2); /* stops at the last */
+ CHECK(press(&app, 5) == PURE_OK && number_view(&app, 2) == 1);
+ CHECK(press(&app, 6) == PURE_OK && !strcmp(view_text(&app, 3), "ax"));
+ CHECK(press(&app, 4) == PURE_OK && app.values[2].num == 2);
+ CHECK(press(&app, 4) == PURE_OK && app.values[2].num == 2); /* stops at the last */
/* Removing the last item: the index moves to the new last. */
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 1 && pure_list_count(app.lists[1]) == 2);
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 0);
+ CHECK(press(&app, 2) == PURE_OK && app.values[2].num == 1 && pure_list_count(app.lists[1]) == 2);
+ CHECK(press(&app, 3) == PURE_OK && app.values[2].num == 0);
/* Removing the current item: the index names the next. */
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 0 && !strcmp(view_text(&app, 3), "ax"));
+ CHECK(press(&app, 2) == PURE_OK && app.values[2].num == 0 && !strcmp(view_text(&app, 3), "ax"));
/* A failing event undoes a set, a remove and an append. */
- CHECK(pure_app_input(&app, 7, PURE_EDGE_DOWN) == PURE_INVALID && app.fault == PURE_FAULT_DIVIDE);
+ CHECK(press(&app, 7) == PURE_INVALID && app.fault == PURE_FAULT_DIVIDE);
CHECK(pure_list_count(app.lists[1]) == 1 && !strcmp(item_title(&app, 1, 0), "ax") &&
pure_list_get(app.lists[1], 0, 1).num == 1 && app.values[2].num == 0 && app.values[0].num == 2);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && pure_list_count(app.lists[1]) == 0 && app.values[2].num == 0);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_NOT_FOUND && app.fault == PURE_FAULT_NOT_FOUND);
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_NOT_FOUND);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && pure_list_count(app.lists[1]) == 1 && app.values[2].num == 0);
- for (int i = 0; i < 3; ++i) CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"\xea\xb0\x80", 3}) == PURE_OK);
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 2);
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 2) == PURE_OK && pure_list_count(app.lists[1]) == 0 && app.values[2].num == 0);
+ CHECK(press(&app, 2) == PURE_NOT_FOUND && app.fault == PURE_FAULT_NOT_FOUND);
+ CHECK(press(&app, 5) == PURE_NOT_FOUND);
+ CHECK(press(&app, 1) == PURE_OK && pure_list_count(app.lists[1]) == 1 && app.values[2].num == 0);
+ for (int i = 0; i < 3; ++i) CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"\xea\xb0\x80", 3}) == PURE_OK);
+ CHECK(press(&app, 3) == PURE_OK && app.values[2].num == 2);
+ CHECK(press(&app, 5) == PURE_OK);
/* Replacement keeps the list by name. The same record keeps the list
* itself; a changed record keeps each field by name and kind and gives new
@@ -2146,11 +2254,11 @@ static void list_mutations(void) {
if (pure_app_start(&app, &module_b, (PureData){0}) != PURE_OK) continue;
for (unsigned e = 0; e < 24; ++e) {
uint32_t input = next_random(&state) % 8;
- if (module_b.input[input].kind == PURE_KIND_TEXT) {
+ if (module_b.input[input].binding == PURE_BIND_TEXT) {
const char *word = words[next_random(&state) % LEN(words)];
- pure_app_text(&app, input, (PureSpan){(const unsigned char *)word, strlen(word)});
- } else if (module_b.input[input].kind == PURE_KIND_KEY) pure_app_input(&app, input, PURE_EDGE_DOWN);
- else pure_app_frame(&app);
+ type_in(&app, input, (PureSpan){(const unsigned char *)word, strlen(word)});
+ } else if (module_b.input[input].binding == PURE_BIND_PRESS) press(&app, input);
+ else frame(&app);
for (uint32_t v = 0; v < module_b.views; ++v) {
double value;
if (module_b.view_type[v] == PURE_VIEW_TEXT) pure_app_view_text(&app, v, ignore_span, NULL);
@@ -2198,7 +2306,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, NULL, 0}, KEY("toggle", "Space")};
+ static const I inputs[] = {{"typed", NULL, TEXT_INPUT, 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 */
@@ -2224,9 +2332,9 @@ static void tables(void) {
double value;
CHECK(pure_app_rows(&app, 1) == 0 && !strcmp(view_text(&app, 0), "empty")); /* the else branch */
CHECK(pure_app_cell_text(&app, 2, 0, text_span, &(Text){{0}, 0}) == PURE_NOT_FOUND);
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"a", 1}) == PURE_OK);
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"\xeb\xb9\xb5", 3}) == PURE_OK);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"a", 1}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"\xeb\xb9\xb5", 3}) == PURE_OK);
+ CHECK(press(&app, 1) == PURE_OK);
CHECK(pure_app_rows(&app, 1) == 2 && !strcmp(view_text(&app, 0), "\xeb\xb9\xb5"));
CHECK(!strcmp(cell_text(&app, 2, 0), "a") && !strcmp(cell_text(&app, 2, 1), "\xeb\xb9\xb5"));
CHECK(!strcmp(cell_text(&app, 3, 0), " ") && !strcmp(cell_text(&app, 3, 1), "x"));
@@ -2334,8 +2442,8 @@ static void table_mutations(void) {
if (pure_app_start(&app, &module_b, (PureData){0}) != PURE_OK) continue;
for (unsigned e = 0; e < 12; ++e) {
uint32_t input = next_random(&state) % 2;
- if (module_b.input[input].kind == PURE_KIND_TEXT) pure_app_text(&app, input, (PureSpan){(const unsigned char *)"t", 1});
- else if (module_b.input[input].kind == PURE_KIND_KEY) pure_app_input(&app, input, PURE_EDGE_DOWN);
+ if (module_b.input[input].binding == PURE_BIND_TEXT) type_in(&app, input, (PureSpan){(const unsigned char *)"t", 1});
+ else if (module_b.input[input].binding == PURE_BIND_PRESS) press(&app, input);
for (uint32_t v = 0; v < module_b.views; ++v) {
double value;
if (!module_b.view_table[v]) {
@@ -2419,14 +2527,14 @@ static void stored_equivalence(Spec spec, const char *const *words, size_t word_
for (unsigned step = 0; step < 400; ++step) {
uint32_t input = next_random(&seed) % module_a.inputs;
PureResult a, b;
- if (module_a.input[input].kind == PURE_KIND_TEXT) {
+ if (module_a.input[input].binding == PURE_BIND_TEXT) {
const char *word = words[next_random(&seed) % word_count];
PureSpan text = {(const unsigned char *)word, strlen(word)};
- a = pure_app_text(&memory, input, text);
- b = pure_app_text(&stored, input, text);
+ a = type_in(&memory, input, text);
+ b = type_in(&stored, input, text);
} else {
- a = pure_app_input(&memory, input, PURE_EDGE_DOWN);
- b = pure_app_input(&stored, input, PURE_EDGE_DOWN);
+ a = press(&memory, input);
+ b = press(&stored, input);
}
CHECK(a == b);
same_states(&memory, &stored);
@@ -2475,7 +2583,7 @@ static void stored_crashes(void) {
CHECK(load(spec, &module_a) == PURE_OK);
PureApp app;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && consistent(&app) == 1);
+ CHECK(press(&app, 0) == PURE_OK && consistent(&app) == 1);
pure_app_stop(&app);
unsigned expected = 1;
for (int stage = PURE_AFTER_HEADER; stage <= PURE_BEFORE_DIR_SYNC; ++stage) {
@@ -2486,7 +2594,7 @@ static void stored_crashes(void) {
if (pure_app_start(&doomed, &module_a, pure_tree_data(path)) != PURE_OK) _exit(1);
crash = 1;
fail_stage = stage;
- pure_app_input(&doomed, 0, PURE_EDGE_DOWN);
+ press(&doomed, 0);
_exit(0);
}
int status;
@@ -2506,7 +2614,7 @@ static void stored_crashes(void) {
if (pure_app_start(&doomed, &module_a, pure_tree_data(path)) != PURE_OK) _exit(1);
crash = 1;
fail_stage = stage;
- pure_app_input(&doomed, 0, PURE_EDGE_DOWN);
+ press(&doomed, 0);
_exit(0);
}
int status;
@@ -2519,28 +2627,28 @@ static void stored_crashes(void) {
* until it starts again, from what the store has. */
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
for (fail_stage = PURE_AFTER_HEADER; fail_stage <= PURE_BEFORE_RENAME; ++fail_stage) {
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_STORE_ERROR && consistent(&app) == expected);
+ CHECK(press(&app, 0) == PURE_STORE_ERROR && consistent(&app) == expected);
}
fail_stage = PURE_BEFORE_DIR_SYNC;
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_UNCERTAIN && consistent(&app) == expected);
+ CHECK(press(&app, 0) == PURE_UNCERTAIN && consistent(&app) == expected);
fail_stage = -1;
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_CONFLICT && consistent(&app) == expected);
+ CHECK(press(&app, 0) == PURE_CONFLICT && consistent(&app) == expected);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && consistent(&app) == expected + 1);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && consistent(&app) == expected + 2);
+ CHECK(press(&app, 0) == PURE_OK && consistent(&app) == expected + 2);
/* The log refusing its entry fails the save and changes nothing; the log
* failing its sync leaves it uncertain: no save follows until the app
* starts again, and then the entry does not count (the tree has not its
* number). */
fail_stage = PURE_LOG_BEFORE_WRITE;
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_STORE_ERROR && consistent(&app) == expected + 2);
+ CHECK(press(&app, 0) == PURE_STORE_ERROR && consistent(&app) == expected + 2);
fail_stage = PURE_LOG_BEFORE_SYNC;
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_UNCERTAIN && consistent(&app) == expected + 2);
+ CHECK(press(&app, 0) == PURE_UNCERTAIN && consistent(&app) == expected + 2);
fail_stage = -1;
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_CONFLICT);
+ CHECK(press(&app, 0) == PURE_CONFLICT);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && consistent(&app) == expected + 2);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && consistent(&app) == expected + 3);
+ CHECK(press(&app, 0) == PURE_OK && consistent(&app) == expected + 3);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && consistent(&app) == expected + 3);
pure_app_stop(&app);
@@ -2569,13 +2677,13 @@ static void uncertain_numbers(void) {
PureApp app;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
fail_stage = PURE_BEFORE_DIR_SYNC;
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_UNCERTAIN && app.uncertain && app.values[0].num == 0);
+ CHECK(press(&app, 0) == PURE_UNCERTAIN && app.uncertain && app.values[0].num == 0);
fail_stage = -1;
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_CONFLICT && !pure_list_count(app.lists[1]));
+ CHECK(press(&app, 1) == PURE_CONFLICT && !pure_list_count(app.lists[1]));
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0].num == 1 &&
!pure_list_count(app.lists[1]));
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && pure_list_get(app.lists[1], 0, 0).num == 1);
+ CHECK(press(&app, 1) == PURE_OK && pure_list_get(app.lists[1], 0, 0).num == 1);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0].num == 1 &&
pure_list_count(app.lists[1]) == 1 && pure_list_get(app.lists[1], 0, 0).num == 1);
@@ -2583,8 +2691,8 @@ static void uncertain_numbers(void) {
remove_store(path);
}
-/* Frame changes are stored by the next event or by a save; a stop alone
- * leaves them out. */
+/* Frames a host defers (027's 4d: a frame is a message, saved as each is unless deferred) are stored by a save or by
+ * the next event it does not defer; a stop alone leaves them out. */
static void stored_frames(void) {
char *path = new_store();
static const O code[] = {{L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}};
@@ -2593,16 +2701,18 @@ static void stored_frames(void) {
NULL, 0, NULL, 0};
CHECK(load(spec, &module_a) == PURE_OK);
PureApp app;
- CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- for (int i = 0; i < 5; ++i) CHECK(pure_app_frame(&app) == PURE_OK);
+ CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && pure_app_defer(&app, true));
+ for (int i = 0; i < 5; ++i) CHECK(frame(&app) == PURE_OK);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0].num == 0);
- for (int i = 0; i < 3; ++i) CHECK(pure_app_frame(&app) == PURE_OK);
+ CHECK(pure_app_defer(&app, true));
+ for (int i = 0; i < 3; ++i) CHECK(frame(&app) == PURE_OK);
CHECK(pure_app_save(&app) == PURE_OK);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0].num == 3);
- for (int i = 0; i < 2; ++i) CHECK(pure_app_frame(&app) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(pure_app_defer(&app, true));
+ for (int i = 0; i < 2; ++i) CHECK(frame(&app) == PURE_OK);
+ CHECK(pure_app_defer(&app, false) && press(&app, 0) == PURE_OK);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0].num == 5);
pure_app_stop(&app);
@@ -2641,7 +2751,7 @@ static void stored_migration(void) {
CHECK(load(before, &module_a) == PURE_OK && load(after, &module_b) == PURE_OK);
PureApp app;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK && press(&app, 0) == PURE_OK);
pure_app_stop(&app);
for (int round = 0; round < 2; ++round) { /* migrated, then stored as migrated */
CHECK(pure_app_start(&app, &module_b, pure_tree_data(path)) == PURE_OK && pure_list_count(app.lists[TASKS]) == 2);
@@ -2653,7 +2763,7 @@ static void stored_migration(void) {
}
/* Back to the first record by replacing the module; the next save stores it. */
CHECK(pure_app_start(&app, &module_b, pure_tree_data(path)) == PURE_OK);
- CHECK(pure_app_replace(&app, &module_a) == PURE_OK && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(pure_app_replace(&app, &module_a) == PURE_OK && press(&app, 0) == PURE_OK);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && consistent(&app) == 3);
pure_app_stop(&app);
@@ -2749,7 +2859,7 @@ static void transient_states(void) {
char *path = new_store();
PureApp app;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK && press(&app, 0) == PURE_OK);
CHECK(app.values[N].num == 2 && app.values[T_].num == 2 && pure_list_count(app.lists[LOG]) == 2 &&
pure_list_count(app.lists[KEEP]) == 2 && app.values[I_].num == 1 && app.values[PICK].num == 1 &&
pure_draft_size(app.texts[NOTE_]) == 3);
@@ -2762,11 +2872,11 @@ static void transient_states(void) {
pure_app_stop(&reader);
pure_snapshot_release(snapshot);
/* A failed event undoes transient states too. */
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) != PURE_OK && app.values[T_].num == 2 &&
+ CHECK(press(&app, 1) != PURE_OK && app.values[T_].num == 2 &&
pure_list_count(app.lists[LOG]) == 2 && pure_draft_size(app.texts[NOTE_]) == 3);
/* Changing only transient states stores nothing. */
uint64_t seq = app.seq;
- for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK);
+ for (int k = 0; k < 3; ++k) CHECK(press(&app, 2) == PURE_OK);
CHECK(app.seq == seq && app.values[T_].num == 5 && pure_list_count(app.lists[LOG]) == 5);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
@@ -2774,10 +2884,10 @@ static void transient_states(void) {
!pure_list_count(app.lists[LOG]) && app.values[I_].num == 0 && app.values[PICK].num == 0 &&
pure_draft_size(app.texts[NOTE_]) == 1);
/* Into b: t, note and log are stored from now on, keep is not. */
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK && press(&app, 0) == PURE_OK);
CHECK(pure_app_replace(&app, &module_b) == PURE_OK && app.values[T_].num == 2 && pure_list_count(app.lists[LOG]) == 2 &&
pure_list_count(app.lists[KEEP]) == 4 && pure_draft_size(app.texts[NOTE_]) == 3);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_b, pure_tree_data(path)) == PURE_OK);
CHECK(app.values[N].num == 5 && app.values[T_].num == 3 && pure_list_count(app.lists[LOG]) == 3 &&
@@ -2812,8 +2922,8 @@ static void stored_states(void) {
typedef struct { uint32_t input; const char *word; } Step;
static PureResult apply_step(PureApp *app, Step step) {
- if (step.word) return pure_app_text(app, step.input, (PureSpan){(const unsigned char *)step.word, strlen(step.word)});
- return pure_app_input(app, step.input, PURE_EDGE_DOWN);
+ if (step.word) return type_in(app, step.input, (PureSpan){(const unsigned char *)step.word, strlen(step.word)});
+ return press(app, step.input);
}
/* Deferred events apply at once and last only through a save: an app that
@@ -2831,7 +2941,7 @@ static void deferred_equivalence(Spec spec, const char *const *words, size_t wor
unsigned lost = 0, saves = 0;
for (unsigned step = 0; step < 600; ++step) {
Step next = {next_random(&seed) % module_a.inputs, NULL};
- if (module_a.input[next.input].kind == PURE_KIND_TEXT) next.word = words[next_random(&seed) % word_count];
+ if (module_a.input[next.input].binding == PURE_BIND_TEXT) next.word = words[next_random(&seed) % word_count];
PureResult a = apply_step(&memory, next), b = apply_step(&stored, next);
CHECK(a == b);
if (a == PURE_OK) history[applied++] = next;
@@ -2869,11 +2979,11 @@ static void deferred_bursts(void) {
CHECK(load(spec, &module_a) == PURE_OK);
PureApp app;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && pure_app_defer(&app, true));
- for (int i = 0; i < 5; ++i) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ for (int i = 0; i < 5; ++i) CHECK(press(&app, 0) == PURE_OK);
CHECK(consistent(&app) == 5 && app.events == 5);
for (fail_stage = PURE_AFTER_HEADER; fail_stage <= PURE_BEFORE_RENAME; fail_stage += 2) {
CHECK(pure_app_save(&app) == PURE_STORE_ERROR && consistent(&app) == 0 && app.events == 0);
- for (int i = 0; i < 5; ++i) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ for (int i = 0; i < 5; ++i) CHECK(press(&app, 0) == PURE_OK);
}
fail_stage = -1;
CHECK(pure_app_save(&app) == PURE_OK && consistent(&app) == 5);
@@ -2887,7 +2997,7 @@ static void deferred_bursts(void) {
if (!child) {
PureApp doomed;
if (pure_app_start(&doomed, &module_a, pure_tree_data(path)) != PURE_OK || !pure_app_defer(&doomed, true)) _exit(1);
- for (int i = 0; i < 7; ++i) if (pure_app_input(&doomed, 0, PURE_EDGE_DOWN) != PURE_OK) _exit(2);
+ for (int i = 0; i < 7; ++i) if (press(&doomed, 0) != PURE_OK) _exit(2);
crash = 1;
fail_stage = stage;
pure_app_save(&doomed);
@@ -2916,7 +3026,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, NULL, 0}, KEY("keep", "KeyK"), KEY("back", "Backspace")};
+ static const I inputs[] = {{"typed", NULL, TEXT_INPUT, 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 */
@@ -2929,13 +3039,13 @@ static void marks(void) {
CHECK(module_a.state_flags[2] == PURE_CURSOR_STAYS && !module_a.state_flags[1]);
PureApp app;
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"x", 1}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"x", 1}) == PURE_OK);
CHECK(!strcmp(state_text(&app, 0), "xab") && app.values[1].num == 1 && app.values[2].num == 0); /* the mark stayed */
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[2].num == 1);
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"yz", 2}) == PURE_OK);
+ CHECK(press(&app, 1) == PURE_OK && app.values[2].num == 1);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"yz", 2}) == PURE_OK);
CHECK(!strcmp(state_text(&app, 0), "xyzab") && app.values[1].num == 3 && app.values[2].num == 1);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[1].num == 2 && app.values[2].num == 1);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[1].num == 1 && app.values[2].num == 1); /* gathered */
+ CHECK(press(&app, 2) == PURE_OK && app.values[1].num == 2 && app.values[2].num == 1);
+ CHECK(press(&app, 2) == PURE_OK && app.values[1].num == 1 && app.values[2].num == 1); /* gathered */
pure_app_stop(&app);
/* A flag only on a cursor, and only this one. */
static const T wrong_flag[] = {{"note", 1, 0}, {"at", 2 | 2 << 8, 0}, {"mark", MARK, 0}};
@@ -2955,7 +3065,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, NULL, 0}, KEY("toggle", "KeyT")};
+static const I fold_inputs[] = {{"typed", NULL, TEXT_INPUT, 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 */
@@ -2990,15 +3100,15 @@ static void folds(void) {
CHECK(!pure_app_view(&app, 6, &value)); /* no first open task yet */
const char *const words[] = {"a", "bbb", "\xea\xb0\x80\xea\xb0\x80"}; /* one, three and two characters */
for (size_t i = 0; i < 3; ++i)
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)words[i], strlen(words[i])}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)words[i], strlen(words[i])}) == PURE_OK);
CHECK(number_view(&app, 0) == 3 && number_view(&app, 1) == 6 && number_view(&app, 2) == 3);
CHECK(number_view(&app, 3) == 0 && number_view(&app, 4) == 1 && number_view(&app, 5) == 13);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* the last task is done */
+ CHECK(press(&app, 1) == PURE_OK); /* the last task is done */
CHECK(number_view(&app, 0) == 2 && number_view(&app, 3) == 2 && number_view(&app, 4) == 1);
CHECK(number_view(&app, 6) == 0); /* the first open one: an index */
- CHECK(pure_app_point(&app, 1, 0) == PURE_OK && pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(place(&app, 1, 0) == PURE_OK && press(&app, 1) == PURE_OK);
CHECK(number_view(&app, 6) == 1);
- CHECK(pure_app_point(&app, 1, 1) == PURE_OK && pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(place(&app, 1, 1) == PURE_OK && press(&app, 1) == PURE_OK);
CHECK(!pure_app_view(&app, 6, &value));
CHECK(number_view(&app, 0) == 0 && number_view(&app, 3) == 6 && !pure_app_view(&app, 4, &value)); /* none open */
pure_app_stop(&app);
@@ -3073,16 +3183,16 @@ static void filters(void) {
size_t at;
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK && pure_app_rows(&app, 0) == 0);
for (const char *word = "abc"; *word; ++word)
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK);
CHECK(pure_app_rows(&app, 0) == 3 && !strcmp(cell_title(&app, 1, 2), "c") && pure_app_cell(&app, 2, 2, &value) && value == 2);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* c is done: it leaves */
+ CHECK(press(&app, 1) == PURE_OK); /* c is done: it leaves */
CHECK(pure_app_rows(&app, 0) == 2 && !pure_app_cell(&app, 2, 2, &value) && !pure_app_item_row(&app, 0, 2, &at));
- CHECK(pure_app_point(&app, 1, 0) == PURE_OK && pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* a too */
+ CHECK(place(&app, 1, 0) == PURE_OK && press(&app, 1) == PURE_OK); /* a too */
CHECK(pure_app_rows(&app, 0) == 1 && !strcmp(cell_title(&app, 1, 0), "b"));
CHECK(pure_app_cell(&app, 2, 0, &value) && value == 1); /* the item's position, not the row's */
CHECK(pure_app_row_item(&app, 0, 0, &at) && at == 1 && !pure_app_row_item(&app, 0, 1, &at));
CHECK(pure_app_item_row(&app, 0, 1, &at) && at == 0 && !pure_app_item_row(&app, 0, 0, &at));
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* a comes back, before b */
+ CHECK(press(&app, 1) == PURE_OK); /* a comes back, before b */
CHECK(pure_app_rows(&app, 0) == 2 && !strcmp(cell_title(&app, 1, 0), "a") && pure_app_item_row(&app, 0, 1, &at) && at == 1);
CHECK(pure_app_replace(&app, &module_a) == PURE_OK && pure_app_rows(&app, 0) == 2);
pure_app_stop(&app);
@@ -3402,7 +3512,7 @@ static void lane_folds(void) {
}
}
passes += pure_app_passes(&a);
- PureResult went_a = pure_app_input(&a, 0, PURE_EDGE_DOWN), went_b = pure_app_input(&b, 0, PURE_EDGE_DOWN);
+ PureResult went_a = press(&a, 0), went_b = press(&b, 0);
if (went_a != went_b || a.fault != b.fault || a.fault_pc != b.fault_pc || a.work != b.work ||
memcmp(a.values, b.values, 4 * sizeof(double))) {
fprintf(stderr, "lane round %u handler: %d %d %u / %d %d %u\n", round, went_a, a.fault, a.fault_pc, went_b, b.fault,
@@ -3546,7 +3656,7 @@ static void walks(void) {
++orders;
}
} else { /* clean */
- result = pure_app_input(&app, 1, PURE_EDGE_DOWN);
+ result = press(&app, 1);
size_t at = 0;
while (at < shop.stocked) {
bool gone = false;
@@ -3566,7 +3676,7 @@ static void walks(void) {
if (!gone) ++at;
}
++cleaned;
- pure_app_input(&app, 1, PURE_EDGE_UP);
+ release(&app, 1);
}
if (!ok) {
shop = before;
@@ -3655,8 +3765,8 @@ static void walk_mutations(void) {
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, (PureSpan){payload, length});
- else if (module_b.input[input].kind == PURE_KIND_KEY)
- pure_app_input(&app, input, PURE_EDGE_DOWN);
+ else if (module_b.input[input].binding == PURE_BIND_PRESS)
+ press(&app, input);
for (uint32_t s_ = 0; s_ < module_b.states; ++s_)
if (module_b.state_type[s_] == PURE_STATE_LIST && module_b.state_flags[s_] & PURE_LIST_ROWS >> 8)
CHECK(!pure_list_count(app.lists[s_])); /* rows go with their event */
@@ -3840,7 +3950,7 @@ static void locals(void) {
items[count] = (double)(next_random(&seed) % 20) - 5;
put_double(payload, items[count++]);
CHECK(pure_app_message(&app, 1, (PureSpan){payload, 8}) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK);
double total = 1, below = 0;
for (size_t i = 0; i < count; ++i) total += items[i];
for (size_t i = 0; i < count; ++i) below += items[i] < total;
@@ -4003,7 +4113,7 @@ static void ranges(void) {
CHECK(pure_app_cell(&a, 7, row, &x) && pure_app_cell(&b, 7, row, &y) && x == y);
}
unsigned input = 1 + next_random(&seed) % 3;
- PureResult went_a = pure_app_input(&a, input, PURE_EDGE_DOWN), went_b = pure_app_input(&b, input, PURE_EDGE_DOWN);
+ PureResult went_a = press(&a, input), went_b = press(&b, input);
CHECK(went_a == went_b && same_lists(&a, &b) && !memcmp(a.values, b.values, 3 * sizeof(double)));
walked += input == 1;
}
@@ -4031,8 +4141,8 @@ static void fold_mutations(void) {
if (pure_app_start(&app, &module_b, (PureData){0}) != PURE_OK) continue;
for (unsigned e = 0; e < 16; ++e) {
uint32_t input = next_random(&state) % 2;
- if (module_b.input[input].kind == PURE_KIND_TEXT) pure_app_text(&app, input, (PureSpan){(const unsigned char *)"ab", 2});
- else if (module_b.input[input].kind == PURE_KIND_KEY) pure_app_input(&app, input, PURE_EDGE_DOWN);
+ if (module_b.input[input].binding == PURE_BIND_TEXT) type_in(&app, input, (PureSpan){(const unsigned char *)"ab", 2});
+ else if (module_b.input[input].binding == PURE_BIND_PRESS) press(&app, input);
for (uint32_t v = 0; v < module_b.views; ++v) {
double value;
size_t item;
@@ -4053,33 +4163,53 @@ static void fold_mutations(void) {
}
}
-/* A host points at an item: the index a view shows as its position moves
- * there, as an event with no handler, saved like a key event; deferred, it
- * joins the burst and a failed save undoes it with the rest. */
+/* A host points at a row (027's 4d): a message bound to the table (one u32 field, the row's item), whose handler moves
+ * the index there; saved like any other event, and deferred, it joins the burst. A pointing names a table. */
static void pointing(void) {
- CHECK(load(list_spec, &module_a) == PURE_OK);
- CHECK(pure_module_position(&module_a, 1) == 2); /* position = at.position */
- CHECK(pure_module_position(&module_a, 0) == -1 && pure_module_position(&module_a, 2) == -1 &&
- pure_module_position(&module_a, 3) == -1 && pure_module_position(&module_a, 99) == -1);
+ enum { XAT_ = PURE_OP_INDEX_AT };
+ static const S nums[] = {{"made", 0}};
+ static const T states[] = {{"tasks", 3, 0}, {"at", 4, 1}};
+ static const RF task_fields[] = {{"title", 1, 0}};
+ static const R records[] = {{"task", task_fields, 1}};
+ static const char *const constants[] = {""};
+ static const I inputs[] = {{"typed", NULL, TEXT_INPUT, NULL, 0}, {"rows.point", "1", POINT_INPUT, NULL, 0}};
+ static const O code[] = {
+ {TI, 0, 0}, {LA, 1, 0}, {XE, 1, 0}, {XS, 2, 0}, /* 0 typed: a task, at it */
+ {FIELD, 0, 0}, {XAT_, 1, 0}, {XS, 2, 0}, /* 4 rows.point: at the row's item */
+ {XL, 2, 0}, {XN, 1, 0}, /* 7 view position */
+ {ROW, 1, 0}, {IG, 1, 0}, /* 9 column title */
+ };
+ static const H handlers[] = {{1, 0, 4}, {3, 4, 3}};
+ static const V views[] = {{"position", 7, 2}};
+ static const W tables[] = {{"rows", 2, 0, 1, 0}, {"title", 1, 2, 9, 2}};
+ Spec spec = {nums, 1, inputs, LEN(inputs), views, 1, handlers, LEN(handlers), code, LEN(code), NULL, 0, states, 2,
+ constants, 1, NULL, 0, records, 1, tables, 2};
+ if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "pointing: %s\n", error); abort(); }
char path[] = "/tmp/pure-point-XXXXXX";
CHECK(mkdtemp(path) && pure_store_init(path) == PURE_OK);
PureApp app;
+ unsigned char row[4];
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- CHECK(pure_app_point(&app, 2, 0) == PURE_NOT_FOUND); /* nothing to point at */
- for (int i = 0; i < 3; ++i) CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"a", 1}) == PURE_OK);
+ CHECK(pure_app_message(&app, 1, (PureSpan){put32(row, 0) - 4, 4}) == PURE_NOT_FOUND); /* nothing to point at */
+ for (int i = 0; i < 3; ++i) CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)"a", 1}) == PURE_OK);
uint64_t events = app.events;
- CHECK(app.values[2].num == 2 && pure_app_point(&app, 2, 0) == PURE_OK && app.values[2].num == 0 && app.events == events + 1);
- CHECK(pure_app_point(&app, 2, 3) == PURE_NOT_FOUND && app.values[2].num == 0);
- CHECK(pure_app_point(&app, 0, 1) == PURE_INVALID && pure_app_point(&app, 1, 1) == PURE_INVALID); /* a number, a list */
- CHECK(pure_app_point(&app, 2, 1) == PURE_OK);
+ CHECK(app.values[2].num == 2 && pure_app_message(&app, 1, (PureSpan){put32(row, 0) - 4, 4}) == PURE_OK);
+ CHECK(app.values[2].num == 0 && app.events == events + 1);
+ CHECK(pure_app_message(&app, 1, (PureSpan){put32(row, 3) - 4, 4}) == PURE_NOT_FOUND && app.values[2].num == 0);
+ CHECK(pure_app_message(&app, 1, (PureSpan){put32(row, 1) - 4, 4}) == PURE_OK);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[2].num == 1);
- CHECK(pure_app_defer(&app, true) && pure_app_point(&app, 2, 2) == PURE_OK && app.open && app.values[2].num == 2);
+ CHECK(pure_app_defer(&app, true) && pure_app_message(&app, 1, (PureSpan){put32(row, 2) - 4, 4}) == PURE_OK && app.open &&
+ app.values[2].num == 2);
CHECK(pure_app_save(&app) == PURE_OK);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[2].num == 2);
pure_app_stop(&app);
remove_store(path);
+ static const W columns_only[] = {{"rows", 1, 0, 9, 2}}; /* no table: a pointing has none to point at */
+ Spec bad = spec;
+ bad.w = columns_only, bad.nw = 1;
+ CHECK(load(bad, &module_b) != PURE_OK && strstr(error, "pointing's table"));
}
/* Keyed lists: items in key order (texts by bytes, numbers by value), each
@@ -4095,9 +4225,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, 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},
+ {"typed", NULL, TEXT_INPUT, NULL, 0}, {"seek", NULL, TEXT_INPUT, NULL, 0},
+ {"has", NULL, TEXT_INPUT, NULL, 0}, KEY("drop", "Delete"), KEY("bump", "KeyB"),
+ KEY("two", "KeyN"), KEY("zero", "KeyO"), KEY("minus_zero", "KeyM"), {"twice", NULL, TEXT_INPUT, 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 */
@@ -4132,7 +4262,7 @@ static Spec keyed_spec(const T *states, const O *code, size_t count, const H *ha
}
static PureResult type_text(PureApp *app, uint32_t input, const char *text) {
- return pure_app_text(app, input, (PureSpan){(const unsigned char *)text, strlen(text)});
+ return type_in(app, input, (PureSpan){(const unsigned char *)text, strlen(text)});
}
/* A list's first field (text) of every item, comma separated. */
@@ -4164,8 +4294,8 @@ static void keyed(void) {
CHECK(type_text(&app, 1, "q") == PURE_NOT_FOUND && app.fault == PURE_FAULT_NOT_FOUND && app.values[K_AT].num == 3);
CHECK(type_text(&app, 2, "m") == PURE_OK && app.values[K_FOUND].num == 1);
CHECK(type_text(&app, 2, "c") == PURE_OK && app.values[K_FOUND].num == 0);
- CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_OK && pure_list_get(app.lists[PEOPLE], 3, 1).num == 1);
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && !strcmp(names_of(&app, PEOPLE), "a,b,m"));
+ CHECK(press(&app, 4) == PURE_OK && pure_list_get(app.lists[PEOPLE], 3, 1).num == 1);
+ CHECK(press(&app, 3) == PURE_OK && !strcmp(names_of(&app, PEOPLE), "a,b,m"));
CHECK(app.values[K_AT].num == 2 && number_view(&app, 2) == 0 && number_view(&app, 3) == 0); /* "m": not bob, not before m */
CHECK(type_text(&app, 1, "b") == PURE_OK && number_view(&app, 2) == 0 && number_view(&app, 3) == 1);
CHECK(type_text(&app, 0, "bob") == PURE_OK && number_view(&app, 2) == 1 && number_view(&app, 3) == 1);
@@ -4174,8 +4304,8 @@ static void keyed(void) {
CHECK(type_text(&app, 0, "") == PURE_OK && type_text(&app, 0, "") == PURE_CONFLICT);
CHECK(!strcmp(names_of(&app, PEOPLE), ",a,ab,b,bob,m,\xea\xb0\x80") && app.values[K_AT].num == 0);
/* Numbers order by value, and -0 is the key 0. */
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_OK && pure_app_input(&app, 6, PURE_EDGE_DOWN) == PURE_OK);
- CHECK(pure_app_input(&app, 7, PURE_EDGE_DOWN) == PURE_CONFLICT && number_view(&app, 4) == 2);
+ CHECK(press(&app, 5) == PURE_OK && press(&app, 6) == PURE_OK);
+ CHECK(press(&app, 7) == PURE_CONFLICT && number_view(&app, 4) == 2);
CHECK(pure_list_get(app.lists[SCORES], 0, 0).num == 0 && !signbit(pure_list_get(app.lists[SCORES], 0, 0).num) &&
pure_list_get(app.lists[SCORES], 1, 0).num == 2);
@@ -4183,7 +4313,7 @@ static void keyed(void) {
CHECK(load(keyed_spec(unkeyed_states, unkeyed_code, LEN(unkeyed_code), unkeyed_handlers, 1), &module_b) == PURE_OK);
CHECK(pure_app_replace(&app, &module_b) == PURE_OK && type_text(&app, 0, "c") == PURE_OK);
CHECK(!strcmp(names_of(&app, PEOPLE), ",a,ab,b,bob,m,\xea\xb0\x80,c") && app.values[K_AT].num == 7);
- CHECK(pure_app_point(&app, K_AT, 6) == PURE_OK); /* at the Korean name */
+ CHECK(place(&app, K_AT, 6) == PURE_OK); /* at the Korean name */
/* Keyed again: put in order, the index following its item. */
CHECK(pure_app_replace(&app, &module_a) == PURE_OK && !strcmp(names_of(&app, PEOPLE), ",a,ab,b,bob,c,m,\xea\xb0\x80"));
CHECK(app.values[K_AT].num == 7 && !strcmp(view_text(&app, 5), "\xea\xb0\x80"));
@@ -4197,7 +4327,7 @@ static void keyed(void) {
CHECK(pure_app_start(&app, &module_b, pure_tree_data(path)) == PURE_OK);
for (const char *const *word = (const char *const[]){"m", "b", "z", NULL}; *word; ++word)
CHECK(type_text(&app, 0, *word) == PURE_OK);
- CHECK(pure_app_point(&app, K_AT, 1) == PURE_OK);
+ CHECK(place(&app, K_AT, 1) == PURE_OK);
pure_app_stop(&app);
for (int round = 0; round < 2; ++round) {
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
@@ -4266,8 +4396,8 @@ static void keyed_mutations(void) {
if (pure_app_start(&app, &module_b, (PureData){0}) != PURE_OK) continue;
for (unsigned e = 0; e < 24; ++e) {
uint32_t input = next_random(&state) % module_b.inputs;
- if (module_b.input[input].kind == PURE_KIND_TEXT) type_text(&app, input, words[next_random(&state) % LEN(words)]);
- else if (module_b.input[input].kind == PURE_KIND_KEY) pure_app_input(&app, input, PURE_EDGE_DOWN);
+ if (module_b.input[input].binding == PURE_BIND_TEXT) type_text(&app, input, words[next_random(&state) % LEN(words)]);
+ else if (module_b.input[input].binding == PURE_BIND_PRESS) press(&app, input);
for (uint32_t v = 0; v < module_b.views; ++v) {
double value;
if (module_b.view_type[v] == PURE_VIEW_TEXT) pure_app_view_text(&app, v, ignore_span, NULL);
@@ -4348,8 +4478,8 @@ static void describe(PureApp *app, Shown *s) {
static void keyed_event(PureApp *app, uint32_t *seed) {
static const char *const names[] = {"a", "b", "ab", "", "\xea\xb0\x80", "zz", "bob", "m", "q", "c"};
uint32_t input = next_random(seed) % app->module->inputs;
- if (app->module->input[input].kind == PURE_KIND_TEXT) type_text(app, input, names[next_random(seed) % LEN(names)]);
- else pure_app_input(app, input, PURE_EDGE_DOWN);
+ if (app->module->input[input].binding == PURE_BIND_TEXT) type_text(app, input, names[next_random(seed) % LEN(names)]);
+ else press(app, input);
}
/* A snapshot shows the states of the save before it, as they were, however
@@ -4547,7 +4677,7 @@ static void snapshot_held(Spec spec, void (*event)(PureApp *, uint32_t *)) {
static void crash_event(PureApp *app, uint32_t *seed) {
(void)seed;
- CHECK(pure_app_input(app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(app, 0) == PURE_OK);
}
static void snapshots(void) {
@@ -5055,10 +5185,10 @@ static void work_budget(void) {
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);
+ CHECK(press(&app, 1) == PURE_LIMIT && app.fault == PURE_FAULT_WORK);
for (size_t i = 0; i < 5; ++i) CHECK(pure_list_get(app.lists[W_STOCK], i, 1).num == 2); /* nothing changed */
pure_app_work(&app, 23 + 22 * 5);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.work == 23 + 22 * 5);
+ CHECK(press(&app, 1) == PURE_OK && app.work == 23 + 22 * 5);
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);
@@ -5074,7 +5204,7 @@ static void work_budget(void) {
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
pure_app_work(&app, UINT64_MAX);
for (const char *word = "abc"; *word; ++word)
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK && app.work == 7);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK && app.work == 7);
double value;
CHECK(number_view(&app, 0) == 3 && app.work == 4 + 3 * 3);
pure_app_work(&app, 4 + 3 * 3 - 1);
@@ -5083,9 +5213,9 @@ static void work_budget(void) {
pure_app_work(&app, 4 + 3 * 3);
CHECK(number_view(&app, 0) == 3);
pure_app_work(&app, 0);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && pure_app_point(&app, 1, 0) == PURE_OK &&
- pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && pure_app_point(&app, 1, 1) == PURE_OK &&
- pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* all done */
+ CHECK(press(&app, 1) == PURE_OK && place(&app, 1, 0) == PURE_OK &&
+ press(&app, 1) == PURE_OK && place(&app, 1, 1) == PURE_OK &&
+ press(&app, 1) == PURE_OK); /* all done */
CHECK(!pure_app_view(&app, 4, &value) && pure_app_view_fault(&app) == PURE_FAULT_NOT_FOUND); /* fewest open: none */
pure_app_stop(&app);
pure_module_release(&module_a);
@@ -5094,7 +5224,7 @@ static void work_budget(void) {
CHECK(load(filter_spec(filter_code, LEN(filter_code), filter_views, 3), &module_a) == PURE_OK);
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
for (const char *word = "abcd"; *word; ++word)
- CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK);
+ CHECK(type_in(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK);
pure_app_work(&app, 3 * 4 - 1);
size_t at;
CHECK(pure_app_rows(&app, 0) == 0 && pure_app_view_fault(&app) == PURE_FAULT_WORK && !pure_app_row_item(&app, 0, 0, &at));
@@ -5134,31 +5264,31 @@ static void memory_budget(void) {
char *path = new_store();
PureApp app;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, ADD, PURE_EDGE_DOWN) == PURE_OK);
+ for (int k = 0; k < 3; ++k) CHECK(press(&app, ADD) == PURE_OK);
size_t three = pure_app_state_bytes(&app);
CHECK(three == 3 * 8); /* a row's number as it is stored */
/* Room for one row more: the next is refused, and nothing of it stays. */
pure_app_memory(&app, three + 8);
- CHECK(pure_app_input(&app, ADD, PURE_EDGE_DOWN) == PURE_OK && pure_list_count(app.lists[1]) == 4);
- CHECK(pure_app_input(&app, ADD, PURE_EDGE_DOWN) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY &&
+ CHECK(press(&app, ADD) == PURE_OK && pure_list_count(app.lists[1]) == 4);
+ CHECK(press(&app, ADD) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY &&
pure_list_count(app.lists[1]) == 4 && pure_app_state_bytes(&app) == three + 8);
/* Over the budget (made smaller), what frees goes on and what grows does not. */
pure_app_memory(&app, 8);
- CHECK(pure_app_input(&app, ADD, PURE_EDGE_DOWN) == PURE_BUDGET);
- CHECK(pure_app_input(&app, DROP, PURE_EDGE_DOWN) == PURE_OK && pure_list_count(app.lists[1]) == 3);
+ CHECK(press(&app, ADD) == PURE_BUDGET);
+ CHECK(press(&app, DROP) == PURE_OK && pure_list_count(app.lists[1]) == 3);
/* Sends count while they wait: a full outbox refuses the next until some are trimmed. */
size_t before = pure_app_state_bytes(&app);
pure_app_memory(&app, before);
- CHECK(pure_app_input(&app, SEND_KEY, PURE_EDGE_DOWN) == PURE_BUDGET && !pure_list_count(app.outbox));
+ CHECK(press(&app, SEND_KEY) == PURE_BUDGET && !pure_list_count(app.outbox));
pure_app_memory(&app, 0);
- CHECK(pure_app_input(&app, SEND_KEY, PURE_EDGE_DOWN) == PURE_OK && pure_list_count(app.outbox) == 1);
+ CHECK(press(&app, SEND_KEY) == PURE_OK && pure_list_count(app.outbox) == 1);
size_t one = pure_app_state_bytes(&app) - before;
pure_app_memory(&app, before + 2 * one);
- CHECK(pure_app_input(&app, SEND_KEY, PURE_EDGE_DOWN) == PURE_OK);
- CHECK(pure_app_input(&app, SEND_KEY, PURE_EDGE_DOWN) == PURE_BUDGET && pure_list_count(app.outbox) == 2);
+ CHECK(press(&app, SEND_KEY) == PURE_OK);
+ CHECK(press(&app, SEND_KEY) == PURE_BUDGET && pure_list_count(app.outbox) == 2);
pure_app_trim(&app, OUT, 1);
CHECK(pure_app_save(&app) == PURE_OK && pure_list_count(app.outbox) == 1);
- CHECK(pure_app_input(&app, SEND_KEY, PURE_EDGE_DOWN) == PURE_OK && outbox_of(&app).items[1].seq == 3);
+ CHECK(press(&app, SEND_KEY) == PURE_OK && outbox_of(&app).items[1].seq == 3);
/* A delivery refused for memory is not heard: given again with room, it is applied once. */
double taken = 7;
pure_app_memory(&app, pure_app_state_bytes(&app));
@@ -5286,19 +5416,19 @@ static void output_meaning(void) {
char *path = new_store();
PureApp app;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pay_reads(&app, 0, 10, 20));
+ CHECK(press(&app, 0) == PURE_OK && pay_reads(&app, 0, 10, 20));
CHECK(pure_app_replace(&app, &module_b) == PURE_OK && app.module == &module_b && pay_reads(&app, 0, 20, 10));
pure_app_stop(&app); /* no save since: the links name a's fields, the outbox's image b's */
CHECK(pure_app_start(&app, &module_b, pure_tree_data(path)) == PURE_OK && pay_reads(&app, 0, 20, 10));
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && pay_reads(&app, 0, 10, 20));
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pay_reads(&app, 1, 10, 20));
+ CHECK(press(&app, 0) == PURE_OK && pay_reads(&app, 1, 10, 20));
CHECK(outbox_of(&app).items[1].seq == 2);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_b, pure_tree_data(path)) == PURE_OK && pay_reads(&app, 0, 20, 10) &&
pay_reads(&app, 1, 20, 10) && outbox_of(&app).items[1].seq == 2);
/* Sent with b's fields, logged after the image named them. */
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pay_reads(&app, 2, 20, 10));
+ CHECK(press(&app, 0) == PURE_OK && pay_reads(&app, 2, 20, 10));
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && pay_reads(&app, 2, 10, 20));
pure_app_stop(&app);
@@ -5357,11 +5487,11 @@ static void bools(void) {
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK && app.values[B_ON].num == 1 && app.values[B_OFF].num == 0);
CHECK(number_view(&app, 0) == 1 && number_view(&app, 1) == 0 && number_view(&app, 2) == 0 && number_view(&app, 3) == 0);
CHECK(!strcmp(view_text(&app, 4), "true1"));
- for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK);
+ for (int k = 0; k < 3; ++k) CHECK(press(&app, 1) == PURE_OK);
CHECK(app.values[B_N].num == 3 && number_view(&app, 1) == 1 && number_view(&app, 3) == 1);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_LIMIT && app.values[B_OFF].num == 0); /* require failed: undone */
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && app.values[B_ON].num == 0 && !strcmp(view_text(&app, 4), "false1"));
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[B_N].num == 3 && number_view(&app, 2) == 1);
+ CHECK(press(&app, 2) == PURE_LIMIT && app.values[B_OFF].num == 0); /* require failed: undone */
+ CHECK(press(&app, 0) == PURE_OK && app.values[B_ON].num == 0 && !strcmp(view_text(&app, 4), "false1"));
+ CHECK(press(&app, 1) == PURE_OK && app.values[B_N].num == 3 && number_view(&app, 2) == 1);
/* A message's bool is a byte, 0 or 1; any other byte fails the message. */
unsigned char payload[9] = {1};
double five = 5;
@@ -5374,7 +5504,7 @@ static void bools(void) {
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);
+ CHECK(press(&app, 3) == PURE_OK);
Outbox o = outbox_of(&app);
double n;
memcpy(&n, o.items[0].payload + 2, 8);
@@ -5382,8 +5512,8 @@ static void bools(void) {
/* Stored, a waiting send keeps its bools. */
char *sends = new_store();
PureApp kept;
- CHECK(pure_app_start(&kept, &module_a, pure_tree_data(sends)) == PURE_OK && pure_app_input(&kept, 1, PURE_EDGE_DOWN) == PURE_OK);
- CHECK(pure_app_input(&kept, 3, PURE_EDGE_DOWN) == PURE_OK && pure_app_save(&kept) == PURE_OK);
+ CHECK(pure_app_start(&kept, &module_a, pure_tree_data(sends)) == PURE_OK && press(&kept, 1) == PURE_OK);
+ CHECK(press(&kept, 3) == PURE_OK && pure_app_save(&kept) == PURE_OK);
pure_app_stop(&kept);
CHECK(pure_app_start(&kept, &module_a, pure_tree_data(sends)) == PURE_OK);
Outbox stored_sends = outbox_of(&kept);
@@ -5522,7 +5652,7 @@ static void bools(void) {
NULL, 0, flagged_records, 1, NULL, 0};
CHECK(load(numbers, &module_a) == PURE_OK && load(flagged, &module_b) == PURE_OK);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ for (int k = 0; k < 3; ++k) CHECK(press(&app, 0) == PURE_OK);
pure_app_stop(&app);
for (int round = 0; round < 2; ++round) { /* migrated, then stored as migrated */
CHECK(pure_app_start(&app, &module_b, pure_tree_data(path)) == PURE_OK && app.values[0].num == 0);
@@ -5568,7 +5698,7 @@ static Ran run_code(const O *code, uint32_t count) {
if (load(spec, &module_b) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
PureApp app;
CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK);
- Ran ran = {pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK ? PURE_FAULT_NONE : app.fault, {{0}}};
+ Ran ran = {press(&app, 0) == PURE_OK ? PURE_FAULT_NONE : app.fault, {{0}}};
memcpy(ran.values, app.values, sizeof(ran.values));
pure_app_stop(&app);
return ran;
@@ -5691,20 +5821,20 @@ static void integers(void) {
CHECK(i64_view(&app, 0) == 0 && !pure_app_view_i64(&app, 1, &value) && !pure_app_view_i64(&app, 4, &value));
double number;
CHECK(!pure_app_view(&app, 0, &number) && pure_app_view(&app, 3, &number) && number == 0);
- for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* -3 steps */
+ for (int k = 0; k < 3; ++k) CHECK(press(&app, 0) == PURE_OK);
+ CHECK(press(&app, 1) == PURE_OK); /* -3 steps */
CHECK(pure_list_count(app.lists[KI_ITEMS]) == 4 && pure_list_get(app.lists[KI_ITEMS], 0, 0).i64 == -3 * STEP &&
pure_list_get(app.lists[KI_ITEMS], 1, 0).i64 == 0 && pure_list_get(app.lists[KI_ITEMS], 3, 0).i64 == 2 * STEP);
CHECK(i64_view(&app, 0) == 0 && i64_view(&app, 1) == 2 * STEP && i64_view(&app, 2) == -3 * STEP &&
pure_app_view(&app, 3, &number) && number == 4 && i64_view(&app, 4) == -3 * STEP);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_CONFLICT && app.fault == PURE_FAULT_EXISTS); /* -3 steps again */
- for (int k = 0; k < 4; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 1) == PURE_CONFLICT && app.fault == PURE_FAULT_EXISTS); /* -3 steps again */
+ for (int k = 0; k < 4; ++k) CHECK(press(&app, 0) == PURE_OK);
CHECK(app.values[KI_NEXT].i64 == INT64_MAX && !pure_app_view_i64(&app, 0, &value)); /* the sum overflows */
CHECK(!strcmp(view_text(&app, 5), "9223372036854775807"));
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_INVALID && app.fault == PURE_FAULT_OVERFLOW);
+ CHECK(press(&app, 0) == PURE_INVALID && app.fault == PURE_FAULT_OVERFLOW);
CHECK(pure_list_count(app.lists[KI_ITEMS]) == 8 && app.values[KI_NEXT].i64 == INT64_MAX && app.values[KI_N].num == 7);
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[KI_FOUND].num == 1);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && i64_view(&app, 4) == 0);
+ CHECK(press(&app, 3) == PURE_OK && app.values[KI_FOUND].num == 1);
+ CHECK(press(&app, 2) == PURE_OK && i64_view(&app, 4) == 0);
/* A message's i64 is 8 bytes, a send's too. */
unsigned char payload[16];
int64_t least = INT64_MIN;
@@ -5846,7 +5976,7 @@ static Byted run_bytes(const O *code, uint32_t count) {
if (load(spec, &module_b) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
PureApp app;
CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK);
- Byted ran = {pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK ? PURE_FAULT_NONE : app.fault, app.values[0].num,
+ Byted ran = {press(&app, 0) == PURE_OK ? PURE_FAULT_NONE : app.fault, app.values[0].num,
app.values[3].i64, {0}, pure_list_count(app.lists[2])};
snprintf(ran.said, sizeof(ran.said), "%s", state_text(&app, 4));
pure_app_stop(&app);
@@ -6029,9 +6159,9 @@ static void formats_and_held(void) {
if (load(spec, &module_b) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
PureApp app;
CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK && press(&app, 0) == PURE_OK);
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(press(&app, 1) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY);
CHECK(!strcmp(state_text(&app, 4), "Az\xc3\xa9")); /* nothing changed */
CHECK(pure_app_message(&app, 2, (PureSpan){NULL, 0}) == PURE_OK);
Text replied = {{0}, 0};
@@ -6094,7 +6224,7 @@ static void lists_in_records(void) {
PureApp app;
CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK);
for (int round = 0; round < 3; ++round) { /* each event begins with its lists and theirs empty */
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK);
if (app.values[0].num != 33443 || app.values[4].i64 != 27) {
fprintf(stderr, "lists in records: n %g, i %" PRId64 "\n", app.values[0].num, app.values[4].i64);
abort();
@@ -6202,21 +6332,21 @@ static void functions_and_characters(void) {
PureApp app;
CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK);
for (int round = 0; round < 3; ++round) { /* the frames' room used again, each event alike */
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 6765);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 18);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 1111 && app.values[0].num == 1);
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && !strcmp(state_text(&app, SAID), "210212"));
- CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 0x61 + 0xE9 + 0x20AC + 0x1F600 &&
+ CHECK(press(&app, 0) == PURE_OK && app.values[IV].i64 == 6765);
+ CHECK(press(&app, 1) == PURE_OK && app.values[IV].i64 == 18);
+ CHECK(press(&app, 2) == PURE_OK && app.values[IV].i64 == 1111 && app.values[0].num == 1);
+ CHECK(press(&app, 3) == PURE_OK && !strcmp(state_text(&app, SAID), "210212"));
+ CHECK(press(&app, 4) == PURE_OK && app.values[IV].i64 == 0x61 + 0xE9 + 0x20AC + 0x1F600 &&
!strcmp(state_text(&app, SAID), constants[1]));
}
/* A call without end: the room runs out (MEMORY) where the work does not; with a smaller work budget, WORK. Either
* way nothing changed, and the lists are the app's own again. */
uint64_t before = (uint64_t)app.values[IV].i64;
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY);
+ CHECK(press(&app, 5) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY);
CHECK((uint64_t)app.values[IV].i64 == before && pure_list_count(app.lists[OWN]) == 0 && app.calls.at == 0 && !app.calls.used);
pure_app_work(&app, 100000);
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_LIMIT && app.fault == PURE_FAULT_WORK);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[IV].i64 == 1111 && app.values[0].num == 1);
+ CHECK(press(&app, 5) == PURE_LIMIT && app.fault == PURE_FAULT_WORK);
+ CHECK(press(&app, 2) == PURE_OK && app.values[IV].i64 == 1111 && app.values[0].num == 1);
pure_app_stop(&app);
/* Characters that fail: within one (é at 2), past the end, a surrogate, past U+10FFFF, below 0. */
const O chars_bad[][3] = {{{TK, 1, 0}, IC(2), {CHAT, 0, 0}}, {{TK, 1, 0}, IC(10), {CHAT, 0, 0}},
@@ -6230,7 +6360,7 @@ static void functions_and_characters(void) {
NULL, 0, records, 2, NULL, 0};
if (load(bad, &module_a) != PURE_OK) { fprintf(stderr, "chars %zu: %s\n", k, error); abort(); }
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) != PURE_OK && app.fault == chars_fault[k]);
+ CHECK(press(&app, 0) != PURE_OK && app.fault == chars_fault[k]);
pure_app_stop(&app);
}
/* Refused: no return; a result of another type; code after a return; RETURN outside a function; a call with an
@@ -6325,7 +6455,7 @@ static void nested_crashes(void) {
char *path = new_store();
PureApp app;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK && press(&app, 0) == PURE_OK);
CHECK(nested_consistent(&app) == 2);
pure_app_stop(&app);
unsigned expected = 2;
@@ -6338,7 +6468,7 @@ static void nested_crashes(void) {
if (pure_app_start(&doomed, &module_a, pure_tree_data(path)) != PURE_OK) _exit(1);
crash = 1;
fail_stage = at;
- pure_app_input(&doomed, 0, PURE_EDGE_DOWN);
+ press(&doomed, 0);
_exit(0);
}
int status;
@@ -6350,11 +6480,11 @@ static void nested_crashes(void) {
crash = 0;
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
for (fail_stage = PURE_AFTER_HEADER; fail_stage <= PURE_BEFORE_RENAME; ++fail_stage) /* a failed commit: nothing */
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_STORE_ERROR && nested_consistent(&app) == expected);
+ CHECK(press(&app, 0) == PURE_STORE_ERROR && nested_consistent(&app) == expected);
fail_stage = PURE_LOG_BEFORE_WRITE;
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_STORE_ERROR && nested_consistent(&app) == expected);
+ CHECK(press(&app, 0) == PURE_STORE_ERROR && nested_consistent(&app) == expected);
fail_stage = -1;
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && nested_consistent(&app) == expected + 1);
+ CHECK(press(&app, 0) == PURE_OK && nested_consistent(&app) == expected + 1);
pure_app_stop(&app);
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && nested_consistent(&app) == expected + 1);
pure_app_stop(&app);
@@ -6385,7 +6515,7 @@ static void nested_replace(void) {
if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "nested replace: %s\n", error); abort(); }
PureApp app;
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
- for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ for (int k = 0; k < 3; ++k) CHECK(press(&app, 0) == PURE_OK);
/* The leaves gain w (7 by default) and the boards' fields swap places. */
static const RF leaf_wider[] = {{"v", 3, 0}, {"w", 0, 7}};
static const RF board_swapped[] = {{"items", 4, 0}, {"id", 3, 0}};
@@ -6558,7 +6688,7 @@ static void anchors(void) {
CHECK(!pure_app_view(&app, 3, &value) && pure_app_view_fault(&app) == PURE_FAULT_NOT_FOUND);
CHECK(!pure_app_view(&app, 4, &value) && pure_app_view_fault(&app) == PURE_FAULT_LIMIT);
CHECK(!pure_app_view(&app, 5, &value) && pure_app_view_fault(&app) == PURE_FAULT_NOT_FOUND);
- for (int k = 0; k < 2; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ for (int k = 0; k < 2; ++k) CHECK(press(&app, 0) == PURE_OK);
CHECK(number_view(&app, 5) == 1);
pure_app_stop(&app);
const struct { O code[4]; uint32_t count; const char *why; } refused[] = {
@@ -6601,14 +6731,14 @@ static void timers(void) {
Spec spec = SPEC(nums, inputs, one_view, handlers, code);
spec.nv = 0;
if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
- CHECK(module_a.input[0].kind == PURE_KIND_TIMER && module_a.input[0].period == 1000);
+ CHECK(module_a.input[0].binding == PURE_BIND_TIMER && module_a.input[0].period == 1000);
PureApp app;
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
app.now = 1700000123456.0;
- CHECK(pure_app_tick(&app, 0) == PURE_OK && app.values[0].num == 1700000123456.0 &&
+ CHECK(tick(&app, 0) == PURE_OK && app.values[0].num == 1700000123456.0 &&
app.values[1].num == (double)(1700000123 % 60) && app.values[2].num == 1);
- CHECK(pure_app_tick(&app, 1) == PURE_INVALID && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_INVALID);
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[3].num == 2 - 20);
+ CHECK(tick(&app, 1) == PURE_INVALID && press(&app, 0) == PURE_INVALID);
+ CHECK(press(&app, 1) == PURE_OK && app.values[3].num == 2 - 20);
/* A zero takes the divisor's sign too. */
static const O zeros[] = {{C, 0, -6}, {C, 0, 3}, {MOD, 0, 0}, {ST, 0, 0}, {C, 0, 6}, {C, 0, -3}, {MOD, 0, 0}, {ST, 1, 0}};
static const H zero_handler[] = {{3, 0, 8}};
@@ -6616,10 +6746,10 @@ static void timers(void) {
signs.o = zeros; signs.no = 8; signs.h = zero_handler; signs.nh = 1;
CHECK(load(signs, &module_b) == PURE_OK);
PureApp zero;
- CHECK(pure_app_start(&zero, &module_b, (PureData){0}) == PURE_OK && pure_app_input(&zero, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(pure_app_start(&zero, &module_b, (PureData){0}) == PURE_OK && press(&zero, 1) == PURE_OK);
CHECK(zero.values[0].num == 0 && !signbit(zero.values[0].num) && zero.values[1].num == 0 && signbit(zero.values[1].num));
pure_app_stop(&zero);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_INVALID && app.fault == PURE_FAULT_DIVIDE);
+ CHECK(press(&app, 2) == PURE_INVALID && app.fault == PURE_FAULT_DIVIDE);
pure_app_stop(&app);
/* A timer's period is 1 ms to a day, and it has nothing else; now is for handlers. */
static const I no_period[] = {TIMER("tick", 0)}, too_long[] = {TIMER("tick", PURE_TIMER_LONGEST + 1)};
@@ -6731,30 +6861,30 @@ static void loops(void) {
CHECK(module_a.state_key[SEEN] == 1 && module_a.state_flags[SEEN] == PURE_LIST_ROWS >> 8);
PureApp app;
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
- for (int k = 0; k < 10; ++k) CHECK(pure_app_input(&app, 9, PURE_EDGE_DOWN) == PURE_OK);
+ for (int k = 0; k < 10; ++k) CHECK(press(&app, 9) == PURE_OK);
CHECK(app.values[N].num == 10 && pure_list_count(app.lists[XS]) == 10);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && app.values[S_].num == 45);
+ CHECK(press(&app, 0) == PURE_OK && app.values[S_].num == 45);
CHECK(app.work == 9 + 10 * (4 + 1)); /* the LOOP counts its block and itself each time round */
- CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[S_].num == 5);
- CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && app.values[S_].num == 100 + 3 * 2 * 100);
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[S_].num == 3 && !pure_list_count(app.lists[SEEN]));
- CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[S_].num == 3); /* each event's own */
- CHECK(pure_app_input(&app, 6, PURE_EDGE_DOWN) == PURE_OK && app.values[S_].num == 3);
+ CHECK(press(&app, 1) == PURE_OK && app.values[S_].num == 5);
+ CHECK(press(&app, 2) == PURE_OK && app.values[S_].num == 100 + 3 * 2 * 100);
+ CHECK(press(&app, 3) == PURE_OK && app.values[S_].num == 3 && !pure_list_count(app.lists[SEEN]));
+ CHECK(press(&app, 3) == PURE_OK && app.values[S_].num == 3); /* each event's own */
+ CHECK(press(&app, 6) == PURE_OK && app.values[S_].num == 3);
/* A loop that would not end runs out of work, by default too, and changes nothing. */
- CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_LIMIT && app.fault == PURE_FAULT_WORK && app.values[S_].num == 3);
+ CHECK(press(&app, 4) == PURE_LIMIT && app.fault == PURE_FAULT_WORK && app.values[S_].num == 3);
CHECK(app.work <= PURE_WORK_DEFAULT && app.work > PURE_WORK_DEFAULT - 5);
- CHECK(pure_app_input(&app, 8, PURE_EDGE_DOWN) == PURE_LIMIT && app.fault == PURE_FAULT_WORK);
- CHECK(pure_app_input(&app, 7, PURE_EDGE_DOWN) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); /* not a whole number */
+ CHECK(press(&app, 8) == PURE_LIMIT && app.fault == PURE_FAULT_WORK);
+ CHECK(press(&app, 7) == PURE_LIMIT && app.fault == PURE_FAULT_LIMIT); /* not a whole number */
/* A list that would fill the memory fails as it passes the default room, or the budget. */
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY &&
+ CHECK(press(&app, 5) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY &&
app.values[S_].num == 3 && !pure_list_count(app.lists[PAGES]));
CHECK(app.grown > (int64_t)PURE_GROWTH_DEFAULT && app.grown < (int64_t)PURE_GROWTH_DEFAULT + 8192);
pure_app_memory(&app, 1 << 20);
- CHECK(pure_app_input(&app, 5, PURE_EDGE_DOWN) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY &&
+ CHECK(press(&app, 5) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY &&
app.grown < (1 << 20) + 8192 && !pure_list_count(app.lists[PAGES]));
pure_app_memory(&app, 0);
pure_app_work(&app, 10); /* a budget of its own: the sum's 9, and not one time round */
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_LIMIT && app.values[S_].num == 3);
+ CHECK(press(&app, 0) == PURE_LIMIT && app.values[S_].num == 3);
pure_app_stop(&app);
/* What the verifier refuses. */
@@ -6868,7 +6998,7 @@ static void view_bodies(void) {
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
double total = 0, value;
for (int k = 0; k < 20; ++k) {
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(press(&app, 0) == PURE_OK);
total += k % 7;
}
for (int round = 0; round < 2; ++round) /* each evaluation begins anew */
@@ -6878,7 +7008,7 @@ static void view_bodies(void) {
pure_app_memory(&app, 4096);
CHECK(!pure_app_view(&app, 3, &value) && pure_app_view_fault(&app) == PURE_FAULT_MEMORY && !pure_list_count(app.lists[SEEN]));
pure_app_memory(&app, 0);
- CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK); /* the app goes on */
+ CHECK(press(&app, 0) == PURE_OK); /* the app goes on */
total += 20 % 7;
/* Readers of one snapshot compute at once, each in its own lists. */
BodyReads reads = {pure_app_snapshot(&app), total, 7, 2000};
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index 3028011..61e053b 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -32,7 +32,9 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를
항목의 바이트 5는 이제 0)와 기준 리비전. 키 있는 리스트가 그 일을 한다(바이트를 담는 레코드 필드, 앱이 가진 리비전). 머리가
38바이트로 줄었고(컬렉션 수가 빠졌다), LEN부터의 명령 번호가 당겨졌다(아래 표). 4c로 **커서와 인덱스가 앵커 하나**가 되었다:
상태 종류 2가 text나 리스트 안의 자리이고(bool은 4, i64는 5), 명령 일곱(ANCHOR_*)이 커서와 인덱스의 열셋을 대신한다. 로드가 그것을
-대상에 맞는 런타임의 명령으로 바꿔 쓴다(module-run.h). 4단계가 끝날 때까지 v7은 걸음마다 바뀐다(1.0 전, 생산자는 aup 하나다).
+대상에 맞는 런타임의 명령으로 바꿔 쓴다(module-run.h). 4d로 **장치의 사건이 메시지**가 되었다: 입력의 종류는 메시지, 출력,
+인자 셋이고, 키의 누름과 뗌, text 입력, 타이머, frame, 가리킴은 메시지 입력의 묶음(무엇이 그 메시지를 만드는가)이다. 핸들러의
+이벤트는 그 입력의 번호이고, DT와 TEXT_INPUT은 필드 읽기(FIELD)가 대신한다(아래 표의 번호가 당겨졌다). 4단계가 끝날 때까지 v7은 걸음마다 바뀐다(1.0 전, 생산자는 aup 하나다).
**레코드 안의 리스트**(027의 2b).
- 레코드 필드의 타입 4(LIST)는 항목들의 레코드를 기본값 자리(i64)에 둔다.
@@ -79,7 +81,7 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를
| 16 | u32 명령 수, u32 문자열 바이트 수 |
| 24 | u16 필드, u16 상수, u16 레코드, u32 상수 블롭 바이트 수, u16 레코드 필드, u16 함수 |
| 38 | 상태 × 16 B: u32 이름, u32 타입(비트 0–7: 0 수, 1 text, 2 앵커(text나 리스트 안의 자리, 027의 4c), 3 리스트, 4 bool, 5 i64. 비트 8: 저장하지 않는 상태(transient, [018](018-lifetimes-and-computation.md)), 그 위는 0. 메시지의 행 목록은 transient가 아니고, transient text나 리스트를 가리키는 앵커는 transient다), 8 B 값: 수와 bool은 f64 초기값(bool은 0이나 1), i64는 초기값(부호 있는 little-endian), text는 u32 초기 상수, 앵커는 u32 대상(text나 리스트 상태)과 u32 플래그(text 안이면 1이 마크: 그 자리에 넣은 글 앞에 남는다. 리스트 안이면 0), 리스트는 u32 레코드와 u32 키(0이면 없음, 아니면 키 필드 + 1, 256이면 메시지의 행 목록: 이름은 그 필드의 경로 `입력.필드`이고 키가 없다) |
-| | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지, 3 text, 4 타이머, 5 출력: 앱이 보내는 메시지, 모양은 메시지와 같고 핸들러가 없다, 6 인자: 한 보기가 받는 인자, 모양은 메시지와 같고 핸들러가 없으며 이름은 `보기.args`), u8 키별 여부, u16 0, u32 키 이름(키 입력)·주기(타이머, 1 ms–24시간을 ms로)·보기 번호(인자) 또는 0, u16 첫 필드, u16 필드 수 |
+| | 입력 × 16 B: u32 이름, u8 종류(1 메시지, 2 출력: 앱이 보내는 메시지, 모양은 메시지와 같고 핸들러가 없다, 3 인자: 한 보기가 받는 인자, 모양은 메시지와 같고 핸들러가 없으며 이름은 `보기.args`), u8 묶음(메시지의 것, 그 메시지를 만드는 것: 0 없음(호스트나 다른 앱이 보낸다), 1 키의 누름, 2 키의 뗌, 3 text 입력, 4 타이머, 5 frame, 6 가리킴), u16 0, u32 키 이름(누름·뗌)·주기(타이머, 1 ms–24시간을 ms로)·보기 번호(인자: 그 보기, 가리킴: 그 표) 또는 0, u16 첫 필드, u16 필드 수 |
| | 필드 × 8 B: u32 이름(식별자, 또는 그룹 안의 필드면 점으로 이은 경로 `user.name`), u32 타입(1 u32le, 2 바이트, 3 수(binary64), 4 text, 5 행: 비트 8–23에 그 행이 채우는 행 목록 상태, 6 bool: 1바이트 0이나 1, 7 i64: 8바이트 부호 있는 little-endian). 페이로드는 접두부와 가변 필드의 바이트다. 접두부에는 선언 순서로 고정 필드의 값과 가변 필드(바이트·text)의 u32 길이가 들어가는데, 마지막 가변 필드는 길이 없이 나머지를 차지한다. 가변 필드의 바이트는 접두부 뒤에 선언 순서로 이어진다(가변 필드가 하나면 예전의 "바이트 꼬리"와 같다) |
| | 상수 × 8 B: u32 블롭 오프셋, u32 길이 (UTF-8) |
| | 레코드 × 8 B: u32 이름, u16 첫 레코드 필드, u16 필드 수(1–16) |
@@ -92,9 +94,9 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를
| | 문자열: `[u8 길이][바이트]`의 연속 |
| | 상수 블롭 (최대 8 KiB) |
-파일 크기는 위 합과 정확히 같아야 한다. 이벤트 번호는 `frame`이 0, 입력 `i`의 누름·도착이 `1 + 2i`,
-키의 뗌이 `2 + 2i`다. 메시지 필드는 입력 순서대로 이어지고, u32le 필드가 차례로 놓인 뒤 바이트 꼬리가
-최대 하나 온다. 메시지 페이로드가 고정 부분보다 짧으면 이벤트는 `limit`으로 실패하고, 남는 바이트는 무시한다.
+파일 크기는 위 합과 정확히 같아야 한다. 핸들러의 이벤트는 그것이 받는 메시지 입력의 번호다(027의 4d. 전에는 `frame`이 0,
+입력 `i`의 누름·도착이 `1 + 2i`, 키의 뗌이 `2 + 2i`였다). 메시지 필드는 입력 순서대로 이어진다. 메시지 페이로드가 고정 부분보다
+짧으면 이벤트는 `limit`으로 실패하고, 남는 바이트는 무시한다.
## 명령
@@ -102,83 +104,81 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를
|---:|---|---|---|
| 0 | CONST | → n 또는 bool | 유한한 값. `인자`가 1이면 bool(값은 +0이나 1) |
| 1 | LOAD | → n 또는 bool | 수나 bool 상태 `인자` |
-| 2 | DT | → n | frame 1/60, 그 밖 0. 핸들러 전용 |
-| 3–6 | ADD SUB MUL DIV | n n → n | DIV에서 0이면 실패 |
-| 7 | NEG | n → n | |
-| 8–13 | LT LE GT GE EQ NE | n n → bool | EQ와 NE는 bool 둘도 받는다 |
-| 14–16 | AND OR NOT | bool bool → bool | NOT은 bool → bool. 양쪽 모두 계산 |
-| 17–18 | MIN MAX | n n → n | IEEE 754-2019: −0 < +0 |
-| 19 | CLAMP | n n n → n | lo > hi이면 실패 |
-| 20 | STORE | n 또는 bool → | 수나 bool 상태 `인자`(그 타입의 값). 핸들러 전용 |
-| 21 | JZ | bool → | 거짓이면 `인자`로 |
-| 22 | JMP | | `인자`로 |
-| 23 | FIELD | → n, bool, b 또는 t | 메시지 필드 `인자`(u32·수는 n, bool은 bool, 바이트는 b, text는 t). 메시지 핸들러와 그 안의 집계, 그리고 인자를 받는 보기(그 표의 열과 필터, 그 안의 집계)에서 인자를 읽는다. 도착할 때 길이가 페이로드를 넘거나 bool의 바이트가 0이나 1이 아니거나 text가 UTF-8이 아니거나 수가 유한하지 않으면 메시지가 `limit`·`encoding`·`non_finite`로 실패한다 |
-| 24 | LEN | b → n | 바이트 값의 길이 |
-| 25 | REPLY | b → | 이 메시지의 응답 |
-| 26 | REQUIRE | bool → | 거짓이면 이벤트가 `limit`으로 실패 |
-| 27 | TEXT_CONST | → t | 상수 `인자` |
-| 28 | TEXT_LOAD | → t | text 상태 `인자`의 현재 값(복사 없는 버전) |
-| 29 | TEXT_INPUT | → t | text 입력의 내용. text 입력 핸들러 전용 |
-| 30 | NUM_TEXT | n → t | 가장 짧은 왕복 10진수. 1e15 미만 정수는 소수점·지수 없이, −0은 `0`. `인자`의 비트 0–1이 1이면 bool → t: `true`·`false`, 2면 i64. 그 위는 형식([027](027-computation-core.md)): 비트 8–15 폭(왼쪽을 채움), 16 부호 뒤를 0으로 채움, 17–19 꼴(PureFormat: 10진, 16진, 대문자 16진, 2진, 8진, 고정 소수), 20–24 고정 소수의 자릿수 + 1. 정수의 꼴에 정수가 아닌 수는 `limit` |
-| 31 | CONCAT | t t → t 또는 b b → b | `인자` 1이면 바이트끼리 |
-| 32–34 | CHARS LINES TEXT_BYTES | t → n | 글자(Unicode scalar) 수, 줄 수(줄바꿈 + 1), 바이트 수 |
-| 35 | ANCHOR_LOAD | → a | 앵커 상태 `인자`(text 안이면 c, 리스트 안이면 i) |
-| 36–37 | ANCHOR_START ANCHOR_END | → a | `인자`가 text면 그 처음·끝(c), 리스트면 첫·마지막 항목(i, 빈 리스트면 0) |
-| 38 | ANCHOR_MOVE | a n → a | `인자`의 비트 0–15는 앵커가 든 text나 리스트, 16–23은 단위(text: 0 글자, 1 줄. 리스트: 0 항목). n만큼, 정수가 아니면 `limit`. 끝에서 멈춘다. 줄 이동은 열을 유지 |
-| 39 | ANCHOR_STORE | a → | 앵커 상태 `인자`. 핸들러 전용. 같은 이벤트에서 지우기 전에 얻은, 끝을 넘은 인덱스는 `not_found` |
-| 40 | TEXT_INSERT | c t → | text `인자`의 커서 위치에 넣는다 |
-| 41 | TEXT_REMOVE | c c → | 두 커서 사이를 지운다 |
-| 42 | TEXT_LINE | c → t | 커서가 있는 줄(줄바꿈 제외) |
-| 43 | TEXT_SET | t → | text 전체를 바꾸고 그 text의 커서를 처음으로 |
-| 44 | TEXT_FROM | b → t | UTF-8이 아니면 이벤트가 실패(encoding) |
-| 45 | BYTES_FROM | t → b | 같은 바이트 |
-| 46 | ANCHOR_COUNT | a → n | 앵커 앞의 단위 수(`인자`는 MOVE와 같다): 글자 수, 0부터 센 줄, 0부터 센 항목의 자리 |
-| 47 | LIST_COUNT | → n | 리스트 상태 `인자`의 항목 수 |
-| 48 | LIST_APPEND | f… → | 레코드 필드 값을 필드 순서대로 받아 끝에 붙인다. 키 있는 리스트에서는 키 순서의 자리에 넣고, 그 키가 이미 있으면 `exists`. 핸들러 전용 |
-| 49 | LIST_INSERT | i f… → | 인덱스의 항목 앞에 넣는다(빈 리스트면 처음). 키 있는 리스트에는 쓸 수 없다 |
-| 50 | LIST_REMOVE | i → | 인덱스의 항목을 지운다. 없으면 `not_found` |
-| 51 | ITEM_GET | i → f | 항목의 필드. `인자`의 아래 16비트는 리스트 상태, 위는 필드 번호. 없으면 `not_found` |
-| 52 | ITEM_SET | i f → | 항목의 필드를 바꾼다. 키 있는 리스트의 키 필드는 바꿀 수 없다. 핸들러 전용 |
-| 53 | ROW | → i | 열이 계산하는 행. `인자`는 그 표의 리스트 상태. 열 전용 |
-| 54 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 |
-| 55 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 |
-| 56 | FOLD | → n 또는 i | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min, 4 first, 5 distinct: 포함된 항목의 서로 다른 값의 수, −0은 0), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 참을 남긴 항목을 세고, sum·max·min·distinct는 블록이 남긴 수와 포함 여부(bool)로 계산한다. first는 블록이 참을 남긴 첫 항목의 인덱스(그 리스트의 인덱스)이고 거기서 멈춘다. 포함된 항목이 없으면 count·sum은 0, max·min·first는 `not_found` |
-| 57 | TEXT_COMPARE | t t → bool | 바이트 순서로 비교한다. `인자`는 관계(0 LT, 1 LE, 2 GT, 3 GE, 4 EQ, 5 NE, 수 비교 명령의 순서). 앞이 같으면 짧은 쪽이 앞이다 |
-| 58 | LIST_FIND | k → i | 키 있는 리스트 `인자`에서 키가 k인 항목의 인덱스. 반씩 나눠 찾는다. 없으면 `not_found` |
-| 59 | LIST_HAS | k → bool | 그 키의 항목이 있으면 참 |
-| 60 | NOW | → n | 이벤트가 보는 지금(1970년부터 ms, UTC). 호스트가 정하고 스크립트는 아무 때나 재현한다. 핸들러 전용: 보기는 상태의 순수 함수로 남는다 |
-| 61 | FLOOR | n → n | 내림 |
-| 62 | MOD | n n → n | 나눗셈을 내림한 나머지. 결과는 나누는 수의 부호를 따른다(0도). 0으로 나누면 `divide` |
-| 63 | SEND | f… → | 출력 `인자`의 필드 값을 필드 순서대로 받아(u32·수는 n, bool은 bool, 바이트는 b, text는 t) 메시지 페이로드(접두부와 가변 필드)로 만들고 이벤트의 일부로 보낸다. u32 필드의 값이 0–2³²−1의 정수가 아니면 `limit`. 핸들러 전용 |
-| 64 | OUTER_ROW | → i | 열 안의 집계 블록에서 그 열의 행(집계의 항목은 ROW). 걷기 안의 집계에서는 걷는 항목 |
-| 65 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 걷기와 LOOP는 서로 안에 8겹까지 둔다. 안쪽의 코드는 바깥 걷기가 도는 리스트에 넣거나 지우지 못한다. 핸들러 전용 |
-| 66 | LOCAL_LOAD | → n, bool 또는 i | 이벤트의 지역 칸. `인자`의 비트 0–7은 칸(64개), 8–15는 담는 것(0 수, 1 인덱스, 2 bool), 16–31은 인덱스의 리스트. 칸은 이벤트가 시작할 때 0이고 저장되지 않는다. 핸들러와 그 안의 걷기, 그 안의 집계에서 읽는다 |
-| 67 | LOCAL_STORE | n, bool, i64 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 담는 것 3은 i64. 한 핸들러는 한 칸을 한 가지(수, bool, i64, 또는 한 리스트의 인덱스)로만 쓴다 |
-| 68–70 | I64_ADD I64_SUB I64_MUL | i64 i64 → i64 | 범위를 넘으면 `overflow` |
-| 71–72 | I64_DIV I64_MOD | i64 i64 → i64 | 내림한 몫과 나머지(나머지는 나누는 수의 부호). 0으로 나누면 `divide`, 가장 작은 i64 / −1은 `overflow` |
-| 73 | I64_NEG | i64 → i64 | 가장 작은 i64는 `overflow` |
-| 74 | I64_COMPARE | i64 i64 → bool | `인자`는 관계(TEXT_COMPARE와 같다) |
-| 75–76 | I64_MIN I64_MAX | i64 i64 → i64 | |
-| 77 | I64_FROM | n → i64 | 범위 안의 정수만. 아니면 `limit` |
-| 78 | NUM_FROM | i64 → n | \|i\| ≤ 2^53일 때만(정확하다). 아니면 `limit` |
-| 79 | LOOP | n n 또는 i i → | 세는 반복([018](018-lifetimes-and-computation.md)). `인자`의 비트 0–7은 지역 칸, 비트 8은 i64의 반복, 16–31은 뒤따르는 블록의 길이 L. 낮음과 높음을 꺼내 그 사이의 값마다(낮음부터 높음 앞까지 1씩) 칸에 값을 두고 L개 명령을 돈다. 수는 2^53 안의 정수여야 하고 아니면 `limit`이다. 블록은 둘레 블록의 코드로 돈다(걷기 안이면 `ROW`도 그대로). 실행량은 돌 때마다 L + 1을 센다. 핸들러 전용 |
-| 80 | BREAK | | 그것이 든 가장 안쪽의 LOOP나 EACH를 끝낸다(그 블록 안, 조건 안에서도). 스택이 빈 자리에서만. 핸들러 전용 |
-| 81 | I64_BITS | i64 i64 → i64 | 끝없는 2의 보수 위의 정수 연산([027](027-computation-core.md)). `인자` 0 and, 1 or, 2 xor, 3 왼쪽 시프트(a·2^b, 범위를 넘으면 `overflow`), 4 오른쪽 시프트(floor(a / 2^b)). 시프트 수가 0–63 밖이면 `limit` |
-| 82 | BYTE_AT | b n 또는 b i64 → i64 | 바이트 값의 그 자리 바이트(0–255). `인자` 0이면 자리가 수, 1이면 i64. 정수가 아니거나 값 밖이면 `limit` |
-| 83 | BYTES_OF | → b | 리스트 상태 `인자`(레코드가 i64 필드 하나)의 항목들을 한 바이트씩. 0–255 밖의 값은 `limit` |
-| 84 | ANCHOR_AT | n → a | 처음부터 n 단위 뒤의 앵커(`인자`는 MOVE와 같다). 정수가 아니면 `limit`, 그 자리가 없으면 `not_found` |
-| 85 | BYTES_SLICE | b n n 또는 b i64 i64 → b | a부터 c 앞까지의 바이트. `인자` 1이면 자리가 i64. 둘 다 정수이고 0 ≤ a ≤ c ≤ 길이가 아니면 `limit` |
-| 86 | BYTE | i64 → b | 한 바이트. 0–255 밖이면 `limit` |
-| 87 | BYTES_LOAD | → b 또는 t | 이름으로 담은 값 `인자`(0–15 바이트, 16–31 text). 이벤트와 보기의 본문이 시작할 때 비어 있다 |
-| 88 | BYTES_STORE | b 또는 t → | 이름으로 담은 값 `인자`의 비트 0–4를 그 값으로(비트 8이면 뒤에 덧붙여). 담은 바이트는 이벤트의 몫에 세고, 넘으면 `memory`. text 칸에는 text만 |
-| 89 | LIST_VALUE | → l | 이벤트의 리스트 상태 `인자`의 값(항목의 복사, 빈 리스트는 0). 아래 "레코드 안의 리스트" |
-| 90 | LIST_BIND | l → | 묶이는 리스트 상태 `인자`(레지스터)가 그 리스트를 읽게 한다 |
-| 91 | APPEND_IN | i f… → | 리스트 상태 `인자`의 비트 0–15(이벤트의 리스트)에서 인덱스의 항목, 그 필드(비트 16–31, 리스트 필드)에 담긴 리스트에 항목을 덧붙인다. 그 리스트를 다른 값이 나눠 가지면 먼저 제 것으로 복사한다 |
-| 92 | NUM_PARSE | t → n | JSON의 수로 쓴 text의 가장 가까운 수. 그런 text가 아니거나 유한하지 않으면 `limit` |
-| 93 | CALL | a… → r… | 함수 `인자`를 부른다. 매개변수를 꺼내고 결과를 놓는다. 위 "함수" |
-| 94 | RETURN | … r… → | 함수 `인자`(자신)를 끝낸다. 맨 위의 결과가 부른 쪽으로 간다 |
-| 95 | CHAR_AT | t q → q | text의 바이트 자리 q(i64)에서 시작하는 글자의 코드 포인트(i64). 글자가 시작하지 않는 자리나 밖이면 `limit` |
-| 96 | CHAR | q → t | 코드 포인트 하나의 text. 0–0x10FFFF 밖이면 `limit`, 서로게이트면 `encoding` |
+| 2–5 | ADD SUB MUL DIV | n n → n | DIV에서 0이면 실패 |
+| 6 | NEG | n → n | |
+| 7–12 | LT LE GT GE EQ NE | n n → bool | EQ와 NE는 bool 둘도 받는다 |
+| 13–15 | AND OR NOT | bool bool → bool | NOT은 bool → bool. 양쪽 모두 계산 |
+| 16–17 | MIN MAX | n n → n | IEEE 754-2019: −0 < +0 |
+| 18 | CLAMP | n n n → n | lo > hi이면 실패 |
+| 19 | STORE | n 또는 bool → | 수나 bool 상태 `인자`(그 타입의 값). 핸들러 전용 |
+| 20 | JZ | bool → | 거짓이면 `인자`로 |
+| 21 | JMP | | `인자`로 |
+| 22 | FIELD | → n, bool, b 또는 t | 메시지 필드 `인자`(u32·수는 n, bool은 bool, 바이트는 b, text는 t). 메시지 핸들러와 그 안의 집계, 그리고 인자를 받는 보기(그 표의 열과 필터, 그 안의 집계)에서 인자를 읽는다. 도착할 때 길이가 페이로드를 넘거나 bool의 바이트가 0이나 1이 아니거나 text가 UTF-8이 아니거나 수가 유한하지 않으면 메시지가 `limit`·`encoding`·`non_finite`로 실패한다 |
+| 23 | LEN | b → n | 바이트 값의 길이 |
+| 24 | REPLY | b → | 이 메시지의 응답 |
+| 25 | REQUIRE | bool → | 거짓이면 이벤트가 `limit`으로 실패 |
+| 26 | TEXT_CONST | → t | 상수 `인자` |
+| 27 | TEXT_LOAD | → t | text 상태 `인자`의 현재 값(복사 없는 버전) |
+| 28 | NUM_TEXT | n → t | 가장 짧은 왕복 10진수. 1e15 미만 정수는 소수점·지수 없이, −0은 `0`. `인자`의 비트 0–1이 1이면 bool → t: `true`·`false`, 2면 i64. 그 위는 형식([027](027-computation-core.md)): 비트 8–15 폭(왼쪽을 채움), 16 부호 뒤를 0으로 채움, 17–19 꼴(PureFormat: 10진, 16진, 대문자 16진, 2진, 8진, 고정 소수), 20–24 고정 소수의 자릿수 + 1. 정수의 꼴에 정수가 아닌 수는 `limit` |
+| 29 | CONCAT | t t → t 또는 b b → b | `인자` 1이면 바이트끼리 |
+| 30–32 | CHARS LINES TEXT_BYTES | t → n | 글자(Unicode scalar) 수, 줄 수(줄바꿈 + 1), 바이트 수 |
+| 33 | ANCHOR_LOAD | → a | 앵커 상태 `인자`(text 안이면 c, 리스트 안이면 i) |
+| 34–35 | ANCHOR_START ANCHOR_END | → a | `인자`가 text면 그 처음·끝(c), 리스트면 첫·마지막 항목(i, 빈 리스트면 0) |
+| 36 | ANCHOR_MOVE | a n → a | `인자`의 비트 0–15는 앵커가 든 text나 리스트, 16–23은 단위(text: 0 글자, 1 줄. 리스트: 0 항목). n만큼, 정수가 아니면 `limit`. 끝에서 멈춘다. 줄 이동은 열을 유지 |
+| 37 | ANCHOR_STORE | a → | 앵커 상태 `인자`. 핸들러 전용. 같은 이벤트에서 지우기 전에 얻은, 끝을 넘은 인덱스는 `not_found` |
+| 38 | TEXT_INSERT | c t → | text `인자`의 커서 위치에 넣는다 |
+| 39 | TEXT_REMOVE | c c → | 두 커서 사이를 지운다 |
+| 40 | TEXT_LINE | c → t | 커서가 있는 줄(줄바꿈 제외) |
+| 41 | TEXT_SET | t → | text 전체를 바꾸고 그 text의 커서를 처음으로 |
+| 42 | TEXT_FROM | b → t | UTF-8이 아니면 이벤트가 실패(encoding) |
+| 43 | BYTES_FROM | t → b | 같은 바이트 |
+| 44 | ANCHOR_COUNT | a → n | 앵커 앞의 단위 수(`인자`는 MOVE와 같다): 글자 수, 0부터 센 줄, 0부터 센 항목의 자리 |
+| 45 | LIST_COUNT | → n | 리스트 상태 `인자`의 항목 수 |
+| 46 | LIST_APPEND | f… → | 레코드 필드 값을 필드 순서대로 받아 끝에 붙인다. 키 있는 리스트에서는 키 순서의 자리에 넣고, 그 키가 이미 있으면 `exists`. 핸들러 전용 |
+| 47 | LIST_INSERT | i f… → | 인덱스의 항목 앞에 넣는다(빈 리스트면 처음). 키 있는 리스트에는 쓸 수 없다 |
+| 48 | LIST_REMOVE | i → | 인덱스의 항목을 지운다. 없으면 `not_found` |
+| 49 | ITEM_GET | i → f | 항목의 필드. `인자`의 아래 16비트는 리스트 상태, 위는 필드 번호. 없으면 `not_found` |
+| 50 | ITEM_SET | i f → | 항목의 필드를 바꾼다. 키 있는 리스트의 키 필드는 바꿀 수 없다. 핸들러 전용 |
+| 51 | ROW | → i | 열이 계산하는 행. `인자`는 그 표의 리스트 상태. 열 전용 |
+| 52 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 |
+| 53 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 |
+| 54 | FOLD | → n 또는 i | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min, 4 first, 5 distinct: 포함된 항목의 서로 다른 값의 수, −0은 0), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 참을 남긴 항목을 세고, sum·max·min·distinct는 블록이 남긴 수와 포함 여부(bool)로 계산한다. first는 블록이 참을 남긴 첫 항목의 인덱스(그 리스트의 인덱스)이고 거기서 멈춘다. 포함된 항목이 없으면 count·sum은 0, max·min·first는 `not_found` |
+| 55 | TEXT_COMPARE | t t → bool | 바이트 순서로 비교한다. `인자`는 관계(0 LT, 1 LE, 2 GT, 3 GE, 4 EQ, 5 NE, 수 비교 명령의 순서). 앞이 같으면 짧은 쪽이 앞이다 |
+| 56 | LIST_FIND | k → i | 키 있는 리스트 `인자`에서 키가 k인 항목의 인덱스. 반씩 나눠 찾는다. 없으면 `not_found` |
+| 57 | LIST_HAS | k → bool | 그 키의 항목이 있으면 참 |
+| 58 | NOW | → n | 이벤트가 보는 지금(1970년부터 ms, UTC). 호스트가 정하고 스크립트는 아무 때나 재현한다. 핸들러 전용: 보기는 상태의 순수 함수로 남는다 |
+| 59 | FLOOR | n → n | 내림 |
+| 60 | MOD | n n → n | 나눗셈을 내림한 나머지. 결과는 나누는 수의 부호를 따른다(0도). 0으로 나누면 `divide` |
+| 61 | SEND | f… → | 출력 `인자`의 필드 값을 필드 순서대로 받아(u32·수는 n, bool은 bool, 바이트는 b, text는 t) 메시지 페이로드(접두부와 가변 필드)로 만들고 이벤트의 일부로 보낸다. u32 필드의 값이 0–2³²−1의 정수가 아니면 `limit`. 핸들러 전용 |
+| 62 | OUTER_ROW | → i | 열 안의 집계 블록에서 그 열의 행(집계의 항목은 ROW). 걷기 안의 집계에서는 걷는 항목 |
+| 63 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 걷기와 LOOP는 서로 안에 8겹까지 둔다. 안쪽의 코드는 바깥 걷기가 도는 리스트에 넣거나 지우지 못한다. 핸들러 전용 |
+| 64 | LOCAL_LOAD | → n, bool 또는 i | 이벤트의 지역 칸. `인자`의 비트 0–7은 칸(64개), 8–15는 담는 것(0 수, 1 인덱스, 2 bool), 16–31은 인덱스의 리스트. 칸은 이벤트가 시작할 때 0이고 저장되지 않는다. 핸들러와 그 안의 걷기, 그 안의 집계에서 읽는다 |
+| 65 | LOCAL_STORE | n, bool, i64 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 담는 것 3은 i64. 한 핸들러는 한 칸을 한 가지(수, bool, i64, 또는 한 리스트의 인덱스)로만 쓴다 |
+| 66–68 | I64_ADD I64_SUB I64_MUL | i64 i64 → i64 | 범위를 넘으면 `overflow` |
+| 69–70 | I64_DIV I64_MOD | i64 i64 → i64 | 내림한 몫과 나머지(나머지는 나누는 수의 부호). 0으로 나누면 `divide`, 가장 작은 i64 / −1은 `overflow` |
+| 71 | I64_NEG | i64 → i64 | 가장 작은 i64는 `overflow` |
+| 72 | I64_COMPARE | i64 i64 → bool | `인자`는 관계(TEXT_COMPARE와 같다) |
+| 73–74 | I64_MIN I64_MAX | i64 i64 → i64 | |
+| 75 | I64_FROM | n → i64 | 범위 안의 정수만. 아니면 `limit` |
+| 76 | NUM_FROM | i64 → n | \|i\| ≤ 2^53일 때만(정확하다). 아니면 `limit` |
+| 77 | LOOP | n n 또는 i i → | 세는 반복([018](018-lifetimes-and-computation.md)). `인자`의 비트 0–7은 지역 칸, 비트 8은 i64의 반복, 16–31은 뒤따르는 블록의 길이 L. 낮음과 높음을 꺼내 그 사이의 값마다(낮음부터 높음 앞까지 1씩) 칸에 값을 두고 L개 명령을 돈다. 수는 2^53 안의 정수여야 하고 아니면 `limit`이다. 블록은 둘레 블록의 코드로 돈다(걷기 안이면 `ROW`도 그대로). 실행량은 돌 때마다 L + 1을 센다. 핸들러 전용 |
+| 78 | BREAK | | 그것이 든 가장 안쪽의 LOOP나 EACH를 끝낸다(그 블록 안, 조건 안에서도). 스택이 빈 자리에서만. 핸들러 전용 |
+| 79 | I64_BITS | i64 i64 → i64 | 끝없는 2의 보수 위의 정수 연산([027](027-computation-core.md)). `인자` 0 and, 1 or, 2 xor, 3 왼쪽 시프트(a·2^b, 범위를 넘으면 `overflow`), 4 오른쪽 시프트(floor(a / 2^b)). 시프트 수가 0–63 밖이면 `limit` |
+| 80 | BYTE_AT | b n 또는 b i64 → i64 | 바이트 값의 그 자리 바이트(0–255). `인자` 0이면 자리가 수, 1이면 i64. 정수가 아니거나 값 밖이면 `limit` |
+| 81 | BYTES_OF | → b | 리스트 상태 `인자`(레코드가 i64 필드 하나)의 항목들을 한 바이트씩. 0–255 밖의 값은 `limit` |
+| 82 | ANCHOR_AT | n → a | 처음부터 n 단위 뒤의 앵커(`인자`는 MOVE와 같다). 정수가 아니면 `limit`, 그 자리가 없으면 `not_found` |
+| 83 | BYTES_SLICE | b n n 또는 b i64 i64 → b | a부터 c 앞까지의 바이트. `인자` 1이면 자리가 i64. 둘 다 정수이고 0 ≤ a ≤ c ≤ 길이가 아니면 `limit` |
+| 84 | BYTE | i64 → b | 한 바이트. 0–255 밖이면 `limit` |
+| 85 | BYTES_LOAD | → b 또는 t | 이름으로 담은 값 `인자`(0–15 바이트, 16–31 text). 이벤트와 보기의 본문이 시작할 때 비어 있다 |
+| 86 | BYTES_STORE | b 또는 t → | 이름으로 담은 값 `인자`의 비트 0–4를 그 값으로(비트 8이면 뒤에 덧붙여). 담은 바이트는 이벤트의 몫에 세고, 넘으면 `memory`. text 칸에는 text만 |
+| 87 | LIST_VALUE | → l | 이벤트의 리스트 상태 `인자`의 값(항목의 복사, 빈 리스트는 0). 아래 "레코드 안의 리스트" |
+| 88 | LIST_BIND | l → | 묶이는 리스트 상태 `인자`(레지스터)가 그 리스트를 읽게 한다 |
+| 89 | APPEND_IN | i f… → | 리스트 상태 `인자`의 비트 0–15(이벤트의 리스트)에서 인덱스의 항목, 그 필드(비트 16–31, 리스트 필드)에 담긴 리스트에 항목을 덧붙인다. 그 리스트를 다른 값이 나눠 가지면 먼저 제 것으로 복사한다 |
+| 90 | NUM_PARSE | t → n | JSON의 수로 쓴 text의 가장 가까운 수. 그런 text가 아니거나 유한하지 않으면 `limit` |
+| 91 | CALL | a… → r… | 함수 `인자`를 부른다. 매개변수를 꺼내고 결과를 놓는다. 위 "함수" |
+| 92 | RETURN | … r… → | 함수 `인자`(자신)를 끝낸다. 맨 위의 결과가 부른 쪽으로 간다 |
+| 93 | CHAR_AT | t q → q | text의 바이트 자리 q(i64)에서 시작하는 글자의 코드 포인트(i64). 글자가 시작하지 않는 자리나 밖이면 `limit` |
+| 94 | CHAR | q → t | 코드 포인트 하나의 text. 0–0x10FFFF 밖이면 `limit`, 서로게이트면 `encoding` |
bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이트의 정수다. CONST의 인자 2는 i64 상수(값의 8바이트), NUM_TEXT의
인자 2는 i64의 10진 글자다. LOAD·STORE·지역 칸·항목·FIELD·SEND는 i64를 수처럼 옮긴다. FOLD의 종류에 64(PURE_FOLD_INTEGER)를
@@ -194,16 +194,18 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이�
- 이름은 ASCII 식별자 1–31바이트이고, 상태·레코드·입력·보기가 한 이름공간을 쓴다. 필드 이름은 메시지나
레코드 안에서 유일하다. 메시지 필드의 이름은 식별자를 점으로 이은 경로일 수 있고(그룹 `user` 안의 `user.name`), 한
이름이 필드이면서 그룹일 수는 없다(`user`와 `user.name`). 레코드(최대 16)의 필드 범위는 차례로 이어지고 모두 합쳐 128개 이하다.
- 키 입력의 키는 정해진 목록(`Space Enter` 화살표 `Backspace Delete Home End Tab Escape PageUp PageDown KeyA–Z Digit0–9`)
- 안에 있고 겹치지 않는다. 메시지 입력은 키 이름이 없고 필드 범위가 차례로 이어진다. text 입력은 필드가 없다.
+ 누름과 뗌의 키는 정해진 목록(`Space Enter` 화살표 `Backspace Delete Home End Tab Escape PageUp PageDown KeyA–Z Digit0–9`)
+ 안에 있고 같은 묶음에서 겹치지 않는다. 입력의 필드 범위는 차례로 이어진다. 묶음마다 필드가 정해져 있다: 누름·뗌·타이머는
+ 없고, text 입력은 text 하나(들어온 글), frame은 수 하나(지난 frame부터의 초, 호스트는 1/60), 가리킴은 u32 하나(그 표의 리스트 안
+ 항목 위치)다. frame에 묶인 입력은 하나까지이고, 가리킴의 보기는 표다.
- 상수는 블롭 안에 있고 유효한 UTF-8이다. text 상태의 초기 상수, 커서의 대상 text, 리스트의 레코드, 인덱스의
대상 리스트, 레코드 필드의 기본값(유한한 수 또는 있는 상수)이 존재한다. bool의 상수, 초기값, 기본값은 +0이나 1이다.
-- 메시지·text 핸들러의 이벤트는 도착(`1 + 2i`)뿐이다.
+- 핸들러의 이벤트는 메시지 입력이다(출력과 인자에는 핸들러가 없다).
- 각 블록에서 명령마다 피연산자 타입(수, bool, 바이트, text, 커서, 인덱스)과 스택 높이(0–64)를 확인한다. bool과 수는 서로의
자리에 쓸 수 없다. 커서 타입은 어느
text의 커서인지, 인덱스 타입은 어느 리스트의 인덱스인지 담는다. 다른 text의 커서나 다른 리스트의 인덱스를 쓰는
모듈은 거절한다. 레코드 명령은 레코드 필드 수만큼 필드 타입대로 꺼낸다. 명령이 놓일 수 있는 문맥(보기,
- 열, 핸들러, 메시지 핸들러, 키별 메시지 핸들러, text 입력 핸들러)도 확인한다. 분기는 블록 안에서 앞으로만 간다.
+ 열, 핸들러, 메시지 핸들러, 함수)도 확인한다. 분기는 블록 안에서 앞으로만 간다.
합류 지점에는 모든 경로가 같은 스택(높이, 타입, 커서·인덱스의 대상)을 가져와야 한다. 그래서 조건식처럼 값이
합류를 건너가도 타입이 섞이지 않는다. 앞에 남은, 값을 가진 합류는 한 번에 8개까지다. 모든 명령에 도달할 수
있어야 하고, 핸들러는 값을 남기지 않으며 보기와 열은 자기 타입의 값 하나를 남긴다.
@@ -227,7 +229,7 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이�
- 보기의 본문(018의 3단계): 표 밖의 보기는 계산할 수 있다. 그 코드는 지역 칸, LOOP, BREAK, EACH를 쓰고 이벤트의 리스트를
바꿀 수 있지만(LIST_APPEND·LIST_INSERT·LIST_REMOVE·ITEM_SET) 다른 상태는 바꾸지 못한다(`view changes a state`). 반복의 블록은
값을 남기지 않는 보기의 문장으로, 걷기의 블록은 그 항목을 읽는 보기의 문장으로 검증한다. 상태 쓰기, SEND, REPLY, REQUIRE,
- DT, NOW는 보기 어디에도 없다. 로드할 때 보기마다 계산하는지(지역 칸이나 반복을 쓰는지)와 바꾸는 이벤트의 리스트를 적는다.
+ NOW는 보기 어디에도 없다. 로드할 때 보기마다 계산하는지(지역 칸이나 반복을 쓰는지)와 바꾸는 이벤트의 리스트를 적는다.
- 이벤트의 리스트: 플래그 256(PURE_LIST_ROWS)을 가진 리스트는 이벤트가 끝날 때마다 비워지고 저장되지 않으며 되돌리기 기록을
쌓지 않는다. 이름은 경로다. `입력.필드`이면 그 메시지의 행 필드가 채우고(키 없이), `let.이름`이면 코드만 채운다(aup의 `let
이름: list of 레코드`, 키를 둘 수 있다: 플래그 256 + 키 필드 + 1). 저장하지 않는 상태(비트 8)일 수 없다.
@@ -335,8 +337,9 @@ text, 끝이 맞지 않는 바이트는 메시지를 `limit`으로 실패시킨�
키 있는 리스트 앱의 메모리·저장 대조(400단계 × 3, 재시작 포함), 검증 거절 8가지, 손상 모듈 20,000개(받아들인 모듈의
키 있는 리스트는 늘 엄격한 키 순서다). aup는 무작위 3,600단계에서 text 키와 수 키 리스트가 파이썬 모델과 같음을 확인한다.
-키·frame·text 이벤트와 타이머의 틱(`pure_app_tick`)은 그 핸들러를 실행한다. 틱은 키 이벤트처럼 저장되는 이벤트다.
-호스트가 앱을 돌리는 동안 타이머의 주기마다 한 번 넣고, 놓친 틱을 몰아서 넣지는 않는다. 이벤트가 보는 시각은
+장치의 사건(키의 누름과 뗌, text 입력, 타이머의 틱, frame, 가리킴)은 그것에 묶인 입력의 메시지다(027의 4d). 호스트가
+`pure_app_message`로 그 입력에 보내고, 다른 메시지처럼 핸들러를 실행해 저장한다. 받는 입력이 없는 사건(핸들러 없는 키의 뗌,
+frame 핸들러 없는 앱의 frame)은 보내지 않는다. 호스트는 앱을 돌리는 동안 타이머의 주기마다 한 번 넣고, 놓친 틱을 몰아서 넣지는 않는다. 이벤트가 보는 시각은
`PureApp.now`이고 호스트가 정한다(`pure-run`의 `time MS`, `tick NAME`). 메시지 이벤트는 다음 순서로 처리한다.
1. 입력 종류를 확인하고, 페이로드가 고정 부분보다 짧으면 `limit`. 행을 행 목록에 채운다.
@@ -352,10 +355,11 @@ text·리스트가 남을 때만 위치를 유지한다.
응답은 `pure_app_reply`로 순서대로 빌려준다. 페이로드를 복사하지 않는다. text 상태의 응답은 확정 전에 복사한다. text 보기는
`pure_app_view_text`로 빌려준다.
-호스트는 인덱스 상태를 한 항목으로 옮길 수 있다(`pure_app_point`, 사용자가 행을 가리키는 것). 핸들러 없는 이벤트이며
-키 이벤트처럼 저장되고, 마지막 항목을 넘으면 `not_found`다. 어느 인덱스를 옮길지는 보기가 알려 준다.
-`pure_module_position`은 수 보기가 인덱스 상태의 위치 그대로(`view tasks_focus = at.position`)이면 그 상태를 돌려준다.
-그래서 인터페이스(Vega)는 앱 코드 없이 목록의 클릭을 포커스로 옮긴다.
+사용자가 표의 행을 가리키는 것은 그 표에 묶인 입력(가리킴)의 메시지다. 필드 하나가 행이 보이는 항목의 리스트 안 위치이고(걸러진
+표면 호스트가 행을 항목으로 바꾼다, `pure_app_row_item`), 그 핸들러가 인덱스를 옮긴다. 마지막 항목을 넘으면 `not_found`다. aup는
+수 보기가 인덱스 상태의 위치 그대로(`view tasks_focus = at.position`)인 표마다 입력 `tasks.point`와 그 핸들러(FIELD 0,
+ANCHOR_AT, ANCHOR_STORE)를 만든다. 그래서 인터페이스(Vega)는 앱 코드 없이 목록의 클릭을 포커스로 옮긴다. 4d 전에는 핸들러 없는
+이벤트(`pure_app_point`)였고, 옮길 인덱스를 보기에서 찾았다(`pure_module_position`).
표 보기는 리스트 항목마다 행 하나를 가진다(`pure_app_rows`). 걸러진 표는 필터 블록이 참을 남긴 항목마다
행 하나를 가진다(항목 순서 그대로). 필터는 상태가 바뀐 뒤 처음 행을 물을 때 항목마다 한 번 돌고, 그 결과(행 → 항목)를
@@ -415,8 +419,8 @@ text·리스트가 남을 때만 위치를 유지한다.
마지막 저장 이후의 이벤트가 **모두** 되돌려진다(값, text draft, 리스트 되돌리기 기록과 저널, 이벤트·tick 수). 그래서
"성공을 알린 이벤트는 저장됐고, 실패를 알린 이벤트는 흔적이 없다"가 성립한다. 결과 불명(`uncertain`)이나 다른
기록자와의 충돌 뒤에는 앱이 저장소와 어긋나 이후 저장이 충돌로 실패한다. 다시 시작하면 저장소의 상태로 이어진다.
-- **기본은 이벤트마다 저장.** 키·text·메시지 이벤트는 저장까지 마치고 돌아온다(묶음 크기 1). 초당 60번 오는
- frame 이벤트는 다음 이벤트나 `pure_app_save`에 함께 들어간다. `pure_app_stop`만 하면 저장하지 않은 변경은 사라진다.
+- **기본은 이벤트마다 저장.** 메시지 이벤트는 저장까지 마치고 돌아온다(묶음 크기 1). 초당 60번 오는 frame은
+ 호스트가 지연 저장으로 보내(Vega의 `vega_frame`, `pure-run`) 다음 이벤트나 `pure_app_save`에 함께 들어간다. `pure_app_stop`만 하면 저장하지 않은 변경은 사라진다.
- **지연 저장(그룹 커밋).** `pure_app_defer(app, true)`면 이벤트는 바로 적용되고, 호스트가 입력을 몰아 처리한 뒤
`pure_app_save` 한 번으로 저장한다. 이벤트는 그 뒤 저장이 성공해야 확정이고, 그 전에는 응답을 내보내지 않는
것이 호스트의 몫이다.
@@ -476,7 +480,7 @@ text·리스트가 남을 때만 위치를 유지한다.
| `GET /views/이름` | 보기의 값. 수는 가장 짧은 왕복 10진수, bool은 `true`·`false`, text는 UTF-8 그대로(`text/plain`). text 상태를 그대로 보이는 보기(`view page = note`)는 저장된 익스텐트를 빌려준다([079](../research/079-lent-views.md)) |
| `GET /views/표?from=N&count=M` | 표의 행: `{"rows":전체,"from":N,"items":[{열: 값}…]}`(`application/json`, bool 열은 `true`·`false`). 요청한 행의 칸만 계산한다. 값이 없는 칸은 `null` |
| `GET /views/이름?인자=값…` | 인자를 받는 보기(표라면 `from`·`count`와 함께). 값은 `%XX`를 풀어 읽고, 고정 필드는 메시지의 쿼리 필드와 같다. 빠진 고정 인자, 모르는 이름, 두 번 준 인자, 타입이 다른 값은 400 `invalid`. 등록한 질의도 같다(`GET /queries/이름?인자=값`) |
-| `POST /inputs/이름` | 이벤트. text 입력은 본문이 text, 키 입력은 누름(down 뒤 up). 메시지는 고정 필드(u32, 수, bool)를 쿼리(`?points=3`, `?done=true`, 그룹 안이면 `?to.zip=7`)로, 가변 필드 하나를 본문으로 받거나, `Content-Type: application/json` 본문의 객체로 필드를 받는다(문자열은 바이트·text, 수는 u32·수, `true`·`false`는 bool, 그룹은 안의 객체). 응답 본문은 핸들러의 `reply`(메시지 필드) |
+| `POST /inputs/이름` | 이벤트. text 입력은 본문이 text, 키의 누름은 누름 뒤 같은 키의 뗌(받는 입력이 있으면). 타이머·frame·가리킴과 출력·인자에는 `no_input`. 메시지는 고정 필드(u32, 수, bool)를 쿼리(`?points=3`, `?done=true`, 그룹 안이면 `?to.zip=7`)로, 가변 필드 하나를 본문으로 받거나, `Content-Type: application/json` 본문의 객체로 필드를 받는다(문자열은 바이트·text, 수는 u32·수, `true`·`false`는 bool, 그룹은 안의 객체). 응답 본문은 핸들러의 `reply`(메시지 필드) |
| `POST /inputs/이름` + `X-Pure-Source`·`X-Pure-Sequence` | 다른 앱의 출력에서 전해진 메시지(`pure_app_deliver`): 번호마다 한 번 적용하고, 이미 들은 번호는 200. 답에 `X-Pure-Batch: 256`을 싣고, `X-Pure-Count: N`이면 본문은 N개 객체의 JSON 배열(연속 번호, 한 묶음에서 차례로 적용) |
- **실패는 실패 종류로 답한다.** 이벤트가 실패하면 `X-Pure-Error`에 종류를 싣는다. `not_found` 404, `exists` 409,
diff --git a/docs/design/027-computation-core.md b/docs/design/027-computation-core.md
index b0a2971..eeebd8b 100644
--- a/docs/design/027-computation-core.md
+++ b/docs/design/027-computation-core.md
@@ -337,6 +337,30 @@
`on frame`과 `dt`는 숨은 프레임 입력과 그 필드로, `input typed = text`의 `typed`는 그 필드로 낸다. `view X_focus =
at.position`은 숨은 가리킴 입력과 `at`을 옮기는 핸들러를 낸다(그래서 앱이 바뀌지 않는다).
- Vega: 키, text, 타이머, 프레임, 가리킴을 묶음을 보고 메시지로 보낸다. serve: 키와 text의 POST를 메시지로, 타이머를 주기 묶음으로.
+ - **만듦(2026-10-18).** 모듈 형식 v7.
+ - 형식: 입력 항목의 종류는 메시지 1, 출력 2, 인자 3이고, 다음 바이트가 묶음이다(없음, 누름, 뗌, text, 타이머, 프레임,
+ 가리킴). 인자 자리는 키 이름(누름·뗌), 주기(타이머), 보기(인자, 가리킴의 표)다. 핸들러의 이벤트는 입력의 번호다. DT와
+ TEXT_INPUT이 빠져 명령은 97개에서 95개가 되었다(DT 뒤의 번호가 하나, TEXT_INPUT 뒤가 둘 당겨졌다).
+ - 로드: 묶음마다 필드의 모양을 확인한다(누름·뗌·타이머는 없음, text는 text 하나, 프레임은 수 하나, 가리킴은 u32 하나와
+ 표인 보기). 프레임 입력은 하나까지다. 키는 묶음 안에서 겹치지 않는다.
+ - 코어에서 빠진 것: `pure_app_input`·`frame`·`tick`·`text`·`point`, `pure_module_position`, `PureEdge`, 앱의 `ticks`. 사건은 모두
+ `pure_app_message`이고 그 뒤에 저장한다(지연 저장이면 묶음의 저장에서).
+ - 호스트: pure-run의 `down`·`up`·`type`·`tick`·`frame`은 묶인 입력에 메시지를 보낸다. 받는 입력이 없는 뗌과 프레임은 사건으로
+ 세고(워크벤치와의 대조가 그대로다) fault를 지운다. 저장소를 가진 앱은 늘 지연 저장으로 돌리고, 프레임이 아닌 명령 뒤에
+ 저장한다(전의 프레임 저장 그대로). pure-serve는 누름의 POST를 누름과 그 키의 뗌으로 넣고, 타이머·프레임·가리킴에는
+ `no_input`이다. Vega는 `vega_frame`(지연 저장으로 dt 1/60), 누름 뒤 뗌, 가리킴(항목 위치의 u32)을 메시지로 보낸다.
+ - aup: 겉은 그대로다. `on k.up`은 숨은 입력 `k.up`(뗌), `on frame`은 숨은 입력 `frame`(필드 `dt`), `view T_focus =
+ at.position`은 숨은 입력 `T.point`와 그 핸들러(FIELD 0, ANCHOR_AT, ANCHOR_STORE)를 낸다. `dt`는 프레임 핸들러에서 그 필드,
+ 다른 곳에서 0이다.
+ - 고친 것: JIT의 계층(tier)이 핸들러의 입력을 옛 번호(`(event − 1) / 2`)로 찾아 메시지의 수 필드를 다른 입력의 필드로
+ 읽었다(aup의 numbers가 잡았다). serve의 `replies_borrow`도 옛 번호를 썼다.
+ - 시험: 단위 시험의 모듈 짓기(`expand`)가 옛 모양의 시험(키, text, 타이머, 이벤트 0과 `2 + 2i`)을 묶인 메시지로 옮겨 쓴다.
+ `pointing`은 가리킴 입력과 그 표로 다시 썼고, `stored_frames`는 호스트처럼 지연 저장으로 프레임을 넣는다. 키의 누름은 필드
+ 없는 메시지라 그 핸들러의 FIELD는 거절된다.
+ - 명령 수(b35 대 b34): 묶음과 함께 BFS +0.03%, 나머지 ±0.001% 안이다. 실행기만은 BFS +0.12%, join +0.01%, 나머지 ±0.001% 안이다.
+ 은행 질의(s34는 4c의 aup 모듈, s35는 4d의 것, 번갈아 각 여섯 번의 중앙값)는 field −0.23%, found −0.14%, large −0.12%,
+ in_credit +0.11%, per_branch +0.06%, ranks +0.04%, none 0.00%다.
+ - 줄 수: 코어 22,287 → 22,221, serve.c 4,978 그대로, aup의 compiler/ 5,820 → 5,896(숨은 입력 셋).
- **4e. 보기는 아무 값(의심 2).**
- 보기의 타입은 레코드 필드의 타입이다. 표는 레코드의 리스트를 내는 보기이고, 열은 그 레코드의 필드다.
- 게으른 칸 계산과 한 번 훑는 집계는 그 구현으로 남는다. 항목이 리스트를 담을 수 있고, 보기의 본문이 지은 리스트도 낼 수 있다.
diff --git a/research/experiments/040-against-c/2026-10-18-counts-s34c.jsonl b/research/experiments/040-against-c/2026-10-18-counts-s34c.jsonl
new file mode 100644
index 0000000..9c7a749
--- /dev/null
+++ b/research/experiments/040-against-c/2026-10-18-counts-s34c.jsonl
@@ -0,0 +1,6 @@
+{"serve": "pure-serve", "per_branch": {"instructions": 27241808, "cycles": 11936398}, "in_credit": {"instructions": 3544166, "cycles": 1596606}, "large": {"instructions": 1429127, "cycles": 613764}, "ranks": {"instructions": 24548046, "cycles": 15021491}, "none": {"instructions": 617940, "cycles": 211324}, "field": {"instructions": 9090056, "cycles": 3300918}, "found": {"instructions": 14242768, "cycles": 11517400}}
+{"serve": "pure-serve", "per_branch": {"instructions": 26978829, "cycles": 11649471}, "in_credit": {"instructions": 3485099, "cycles": 1601801}, "large": {"instructions": 1429199, "cycles": 614198}, "ranks": {"instructions": 24403224, "cycles": 14777297}, "none": {"instructions": 617940, "cycles": 192347}, "field": {"instructions": 9090056, "cycles": 3286492}, "found": {"instructions": 14244568, "cycles": 10697999}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27222597, "cycles": 11845946}, "in_credit": {"instructions": 3539828, "cycles": 1617340}, "large": {"instructions": 1429087, "cycles": 610887}, "ranks": {"instructions": 24537643, "cycles": 15097164}, "none": {"instructions": 617940, "cycles": 200272}, "field": {"instructions": 9090056, "cycles": 3296117}, "found": {"instructions": 14241520, "cycles": 10631980}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27130318, "cycles": 11749744}, "in_credit": {"instructions": 3519073, "cycles": 1586591}, "large": {"instructions": 1429143, "cycles": 610543}, "ranks": {"instructions": 24486572, "cycles": 14926619}, "none": {"instructions": 617940, "cycles": 191799}, "field": {"instructions": 9090056, "cycles": 3292585}, "found": {"instructions": 14241569, "cycles": 10519417}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27143006, "cycles": 11829531}, "in_credit": {"instructions": 3521918, "cycles": 1592510}, "large": {"instructions": 1429063, "cycles": 612123}, "ranks": {"instructions": 24493167, "cycles": 14957482}, "none": {"instructions": 617940, "cycles": 190022}, "field": {"instructions": 9090056, "cycles": 3297456}, "found": {"instructions": 14241608, "cycles": 10726223}}
+{"serve": "pure-serve", "per_branch": {"instructions": 26968012, "cycles": 11648064}, "in_credit": {"instructions": 3482584, "cycles": 1625366}, "large": {"instructions": 1429159, "cycles": 622308}, "ranks": {"instructions": 24395932, "cycles": 14728362}, "none": {"instructions": 618043, "cycles": 191009}, "field": {"instructions": 9090057, "cycles": 3306237}, "found": {"instructions": 14243385, "cycles": 10733720}}
diff --git a/research/experiments/040-against-c/2026-10-18-counts-s35.jsonl b/research/experiments/040-against-c/2026-10-18-counts-s35.jsonl
new file mode 100644
index 0000000..62903f7
--- /dev/null
+++ b/research/experiments/040-against-c/2026-10-18-counts-s35.jsonl
@@ -0,0 +1,6 @@
+{"serve": "pure-serve", "per_branch": {"instructions": 27134298, "cycles": 11770920}, "in_credit": {"instructions": 3519964, "cycles": 1604441}, "large": {"instructions": 1427444, "cycles": 607949}, "ranks": {"instructions": 24487384, "cycles": 14887418}, "none": {"instructions": 617935, "cycles": 190996}, "field": {"instructions": 9069360, "cycles": 3271818}, "found": {"instructions": 14221946, "cycles": 10571214}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27271438, "cycles": 11957396}, "in_credit": {"instructions": 3550870, "cycles": 1614461}, "large": {"instructions": 1427404, "cycles": 604385}, "ranks": {"instructions": 24563270, "cycles": 15249598}, "none": {"instructions": 618038, "cycles": 207684}, "field": {"instructions": 9069257, "cycles": 3299948}, "found": {"instructions": 14219410, "cycles": 10737436}}
+{"serve": "pure-serve", "per_branch": {"instructions": 26951202, "cycles": 11671165}, "in_credit": {"instructions": 3478877, "cycles": 1571415}, "large": {"instructions": 1427452, "cycles": 605258}, "ranks": {"instructions": 24385789, "cycles": 14773674}, "none": {"instructions": 617935, "cycles": 189579}, "field": {"instructions": 9069360, "cycles": 3270389}, "found": {"instructions": 14223058, "cycles": 11102582}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27173890, "cycles": 11860682}, "in_credit": {"instructions": 3528990, "cycles": 1590021}, "large": {"instructions": 1427348, "cycles": 607124}, "ranks": {"instructions": 24510286, "cycles": 14855785}, "none": {"instructions": 617935, "cycles": 189867}, "field": {"instructions": 9069257, "cycles": 3279728}, "found": {"instructions": 14220113, "cycles": 10638247}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27191279, "cycles": 11911258}, "in_credit": {"instructions": 3532712, "cycles": 1600316}, "large": {"instructions": 1427348, "cycles": 601054}, "ranks": {"instructions": 24519231, "cycles": 16239226}, "none": {"instructions": 617935, "cycles": 204242}, "field": {"instructions": 9069257, "cycles": 3294470}, "found": {"instructions": 14221233, "cycles": 10685197}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27054210, "cycles": 11369237}, "in_credit": {"instructions": 3502013, "cycles": 1598130}, "large": {"instructions": 1427452, "cycles": 599999}, "ranks": {"instructions": 24443319, "cycles": 14850028}, "none": {"instructions": 617935, "cycles": 195503}, "field": {"instructions": 9069257, "cycles": 3269386}, "found": {"instructions": 14222129, "cycles": 10753065}}
diff --git a/research/experiments/041-batches/2026-10-18-counts.jsonl b/research/experiments/041-batches/2026-10-18-counts.jsonl
index 74a03d2..d008b81 100644
--- a/research/experiments/041-batches/2026-10-18-counts.jsonl
+++ b/research/experiments/041-batches/2026-10-18-counts.jsonl
@@ -98,3 +98,13 @@
{"version":"b34","way":"rounds","work":"join","instructions":7677476459,"cycles":3286100652}
{"version":"b34","way":"batches","work":"bfs","instructions":3346476311,"cycles":1665790741}
{"version":"b34","way":"rounds","work":"bfs","instructions":4703444335,"cycles":2053412578}
+{"version":"b35","way":"batches","work":"roots","instructions":157260491,"cycles":109536961}
+{"version":"b35","way":"rounds","work":"roots","instructions":2719818489,"cycles":889047727}
+{"version":"b35","way":"batches","work":"knapsack","instructions":348347128,"cycles":105048294}
+{"version":"b35","way":"rounds","work":"knapsack","instructions":21991195875,"cycles":7367662407}
+{"version":"b35","way":"batches","work":"join","instructions":1783683060,"cycles":1031662745}
+{"version":"b35","way":"rounds","work":"join","instructions":7678075995,"cycles":3174628071}
+{"version":"b35","way":"batches","work":"matmul","instructions":703679580,"cycles":231377121}
+{"version":"b35","way":"rounds","work":"matmul","instructions":21259969393,"cycles":6205837331}
+{"version":"b35","way":"batches","work":"bfs","instructions":3347488283,"cycles":1696370332}
+{"version":"b35","way":"rounds","work":"bfs","instructions":4709045175,"cycles":2054010986}
diff --git a/serve/serve.c b/serve/serve.c
index 500f470..81479ba 100644
--- a/serve/serve.c
+++ b/serve/serve.c
@@ -900,10 +900,10 @@ static const char *resolve(Connection *c, char *query, Route *route, int *status
return NULL;
}
int found = route->kind == ROUTE_VIEW ? pure_module_view(m, c->key) : pure_module_input(m, c->key);
- /* An output is the app's to send and a timer ticks by itself: neither is posted. */
+ /* An output is the app's to send, and a host makes a tick, a frame and a pointing: none is posted. */
if (found >= 0 && route->kind == ROUTE_INPUT &&
- (m->input[found].kind == PURE_KIND_OUTPUT || m->input[found].kind == PURE_KIND_TIMER ||
- m->input[found].kind == PURE_KIND_ARGUMENTS)) found = -1;
+ (m->input[found].kind != PURE_KIND_MESSAGE || m->input[found].binding == PURE_BIND_TIMER ||
+ m->input[found].binding == PURE_BIND_FRAME || m->input[found].binding == PURE_BIND_POINT)) found = -1;
if (found < 0) return route->kind == ROUTE_VIEW ? "no_view" : "no_input";
*status = 400;
route->target = (uint32_t)found;
@@ -914,7 +914,7 @@ static const char *resolve(Connection *c, char *query, Route *route, int *status
if (*c->source && !message) return "delivery";
if (!app->owned) c->source[0] = 0; /* an app without states or outputs applies it as a message */
if (shape) {
- /* A body is a text input's text, a message's one variable field (rows
+ /* A body is a message's one variable field (a text input's text; rows
* in their list's stored form), or a JSON object of a message's fields
* (two variable fields need one). */
unsigned variables = 0;
@@ -923,8 +923,7 @@ static const char *resolve(Connection *c, char *query, Route *route, int *status
m->field[shape->field_start + f].type == PURE_FIELD_TEXT ||
m->field[shape->field_start + f].type == PURE_FIELD_ROWS;
if (c->json && !message) return "json";
- if (!c->json && (variables > 1 || (c->body_size && shape->kind == PURE_KIND_KEY) ||
- (c->body_size && message && !variables))) return variables > 1 ? "json" : "body";
+ if (!c->json && (variables > 1 || (c->body_size && !variables))) return variables > 1 ? "json" : "body";
c->input = (uint32_t)found;
c->field_bytes = message ? shape->prefix : 0;
}
@@ -1633,11 +1632,12 @@ static PureResult apply_input(PureApp *app, const Route *route, const PureEffect
size_t lead = 4 + (header & DELIVERED ? DELIVERY : 0), table = header & PLACED ? 8 * (size_t)shape->field_count : 0;
PureSpan payload = {effect->value.data + lead + batch * table, header & ~(PLACED | DELIVERED)};
PureResult status;
- if (shape->kind == PURE_KIND_TEXT) {
- status = pure_app_text(app, route->target, payload);
- } else if (shape->kind == PURE_KIND_KEY) {
- status = pure_app_input(app, route->target, PURE_EDGE_DOWN);
- if (status == PURE_OK) status = pure_app_input(app, route->target, PURE_EDGE_UP);
+ if (shape->binding == PURE_BIND_PRESS) { /* a key pressed: its press, and its release when a message takes it */
+ const PureModule *m = app->module;
+ status = pure_app_message(app, route->target, (PureSpan){0});
+ for (uint32_t i = 0; status == PURE_OK && i < m->inputs; ++i)
+ if (m->input[i].binding == PURE_BIND_RELEASE && !strcmp(m->input_keys[i], m->input_keys[route->target]))
+ status = pure_app_message(app, i, (PureSpan){0});
} else {
uint32_t placed[2 * PURE_MODULE_FIELDS];
for (size_t i = 0; table && i < 2 * (size_t)shape->field_count; ++i)
@@ -2114,11 +2114,11 @@ static long run_timers(Executor *ex) {
uint64_t now = now_ms(), soonest = UINT64_MAX;
bool ticked = false;
for (uint32_t i = 0; i < m->inputs; ++i) {
- if (m->input[i].kind != PURE_KIND_TIMER) continue;
+ if (m->input[i].binding != PURE_BIND_TIMER) continue;
if (!ex->next[i]) ex->next[i] = now + m->input[i].period; /* the first period starts now */
if (ex->next[i] <= now) {
ex->app.now = wall_ms();
- if (pure_app_tick(&ex->app, i) == PURE_OK) ++ex->ticks;
+ if (pure_app_message(&ex->app, i, (PureSpan){0}) == PURE_OK) ++ex->ticks;
ticked = true;
ex->next[i] = now + m->input[i].period;
}
@@ -3138,7 +3138,7 @@ static void surface_release(PureSurfaceHost *s, PureSnapshot *snapshot) {
static PureResult surface_event(PureSurfaceHost *s, uint32_t input, PureSpan text) {
Owner *owner = s->owner;
const PureModule *m = owner->executor.module;
- if (input >= m->inputs || (m->input[input].kind != PURE_KIND_KEY && m->input[input].kind != PURE_KIND_TEXT) ||
+ if (input >= m->inputs || (m->input[input].binding != PURE_BIND_PRESS && m->input[input].binding != PURE_BIND_TEXT) ||
(text.size && !text.data))
return PURE_INVALID;
if (text.size > PURE_OBJECT_LIMIT) return PURE_LIMIT;
@@ -3167,14 +3167,14 @@ static PureResult surface_event(PureSurfaceHost *s, uint32_t input, PureSpan tex
static PureResult surface_press(PureSurfaceHost *s, uint32_t input) {
return input < s->owner->executor.module->inputs &&
- s->owner->executor.module->input[input].kind == PURE_KIND_KEY
+ s->owner->executor.module->input[input].binding == PURE_BIND_PRESS
? surface_event(s, input, (PureSpan){0})
: PURE_INVALID;
}
static PureResult surface_type(PureSurfaceHost *s, uint32_t input, PureSpan text) {
return input < s->owner->executor.module->inputs &&
- s->owner->executor.module->input[input].kind == PURE_KIND_TEXT
+ s->owner->executor.module->input[input].binding == PURE_BIND_TEXT
? surface_event(s, input, text)
: PURE_INVALID;
}
@@ -3216,7 +3216,7 @@ static bool start_surface(PureSurfaceHost *s, Owner *owner, int argc, char **arg
* served by PUT or POST may reply only a field of its message. */
static bool replies_borrow(const PureModule *m, uint32_t input) {
for (uint32_t h = 0; h < m->handlers; ++h) {
- if (m->handler[h].event != 1 + 2 * input) continue;
+ if (m->handler[h].event != input) continue;
const PureBlock *code = &m->handler[h].code;
/* A field replied as it is, or a text field as its bytes. */
for (uint32_t pc = code->start; pc < code->start + code->length; ++pc) {
diff --git a/tests/module-probe.c b/tests/module-probe.c
index de1fbf8..701b6aa 100644
--- a/tests/module-probe.c
+++ b/tests/module-probe.c
@@ -6,7 +6,7 @@
*
* module-probe FILE.pmod INPUT...
*
- * Each INPUT is a key input's name, pressed. The module must not read the
+ * Each INPUT is a key input's name, pressed (its message, no fields). The module must not read the
* clock (now): the two runs are not at the same time. Prints a line of
* JSON: the inputs applied, the ops ranged, and each number state's value;
* exits 1 at the first difference, naming the state. */
@@ -92,7 +92,7 @@ int main(int argc, char **argv) {
return 2;
}
PureResult results[2];
- for (int k = 0; k < 2; ++k) results[k] = pure_app_input(&apps[k], (uint32_t)input, PURE_EDGE_DOWN);
+ for (int k = 0; k < 2; ++k) results[k] = pure_app_message(&apps[k], (uint32_t)input, (PureSpan){0});
int state = differs(&modules[0], &apps[0], &apps[1]);
if (results[0] != results[1] || state >= 0) {
fprintf(stderr, "module-probe: after %s (input %d): %s differs ranged and not (results %d, %d)\n", argv[i],