aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 20:01:08 +0100
committereunhhu <[email protected]>2026-10-18 20:01:08 +0100
commit936c08e3424c606855051ee86f25a7df0f162c62 (patch)
tree94870b4892d616e420bf259f3031f960c4bf28cc /core
parentb22b8c3f6c5378b47c36370225d7c41d4394bb7c (diff)
downloadpure-936c08e3424c606855051ee86f25a7df0f162c62.tar.gz
pure-936c08e3424c606855051ee86f25a7df0f162c62.zip
Module format v7, 027's 4c: cursors and indices one anchor: a module's state type 2 is a place in the text or the list it names (a bool 4, an i64 5), and seven anchor instructions take the place of the thirteen of cursors and indices (103 opcodes now 97): LOAD and STORE an anchor state, START and END of a text or a list, MOVE, COUNT and AT naming it and a unit (a character, a line, an item); the load holds an anchor in a text as a cursor and in a list as an index (a mark in a list refused) and each anchor instruction as the runtime's own one for it (PURE_OP_CUR_*, PURE_OP_INDEX_*, numbered past a module's in module-run.h with the kept lists' steps; a module holding one refused), so the executor's, the batches' and the JIT's steps are those they were; the verifier holds MOVE, COUNT and AT to the text or list they name; new: AT in a text (a cursor so many characters or lines from its start, not_found past the end); COUNT's lines from 0; unit test anchors (AT and COUNT in a text, AT in a list, four refused); the tests' modules written with the anchor instructions; counts: b34 against b33 with batches matmul -1.36%, the rest within 0.08%, without batches within 0.04%; the bank's queries (six each, s33 on the 4b-2 aup's module) in_credit +0.28%, per_branch +0.23%, ranks +0.10%, the rest 0.00%; 008 and 027
Diffstat (limited to 'core')
-rw-r--r--core/module-jit.c2
-rw-r--r--core/module-run.h12
-rw-r--r--core/module-text.c2
-rw-r--r--core/module-text.h2
-rw-r--r--core/module.c125
-rw-r--r--core/module.h26
-rw-r--r--core/test-module.c137
7 files changed, 237 insertions, 69 deletions
diff --git a/core/module-jit.c b/core/module-jit.c
index d5c5d3a..3a3a354 100644
--- a/core/module-jit.c
+++ b/core/module-jit.c
@@ -5763,7 +5763,7 @@ static bool worth(const PureModule *m, PureBlock block) {
int input = tier_input(m, block);
for (uint32_t pc = block.start; pc < block.start + block.length; pc = after(&m->code[pc], pc)) {
const PureOp *op = &m->code[pc];
- if (op->op == PURE_OP_CALL || op->op >= PURE_OP_COUNT) return false; /* the executor's frames, a kept list's steps */
+ if (op->op == PURE_OP_CALL || op->op >= PURE_OP_KEPT_GET) return false; /* the executor's frames, a kept list's steps */
if ((op->op == PURE_OP_LOCAL_LOAD || op->op == PURE_OP_LOCAL_STORE) && (op->arg >> 8 & 0xFF) == 4) return false; /* a list */
if (op->op == PURE_OP_EACH || op->op == PURE_OP_LOOP) {
loops = true;
diff --git a/core/module-run.h b/core/module-run.h
index 2ad87c6..ceaa902 100644
--- a/core/module-run.h
+++ b/core/module-run.h
@@ -7,6 +7,18 @@
#include <math.h>
#include <string.h>
+/* The runtime's own instructions, past a module's (no module holds one): what the load makes of a module's anchor
+ * instructions by what their anchor is in (027's 4c: a cursor's in a text, an index's in a list; the same arguments),
+ * and a kept list's lists' steps (027's stage 3). */
+enum {
+ PURE_OP_CUR_LOAD = PURE_OP_COUNT, PURE_OP_CUR_START, PURE_OP_CUR_END, PURE_OP_CUR_MOVE, PURE_OP_CUR_STORE,
+ PURE_OP_CUR_COUNT, PURE_OP_CUR_AT, PURE_OP_INDEX_LOAD, PURE_OP_INDEX_STORE, PURE_OP_INDEX_MOVE, PURE_OP_INDEX_FIRST,
+ PURE_OP_INDEX_LAST, PURE_OP_INDEX_NUM, PURE_OP_INDEX_AT,
+ PURE_OP_KEPT_GET, PURE_OP_KEPT_SET, PURE_OP_KEPT_APPEND, PURE_OP_KEPT_INSERT, PURE_OP_KEPT_APPEND_IN,
+ PURE_OP_STEPS
+};
+_Static_assert(PURE_OP_STEPS <= 256, "an instruction's op is a byte");
+
/* Read from a message's bytes (little-endian). */
static inline uint32_t read32(const unsigned char *p) {
return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24;
diff --git a/core/module-text.c b/core/module-text.c
index f09cb75..bc1279a 100644
--- a/core/module-text.c
+++ b/core/module-text.c
@@ -238,5 +238,5 @@ static bool count_span(void *context, PureSpan span) {
size_t pure_text_count(const PureDraft *draft, PureVersion version, size_t position, unsigned unit) {
Count c = {0, unit};
if (position) pure_draft_read_version(draft, version, 0, position, count_span, &c);
- return c.count + (unit ? 1 : 0);
+ return c.count;
}
diff --git a/core/module-text.h b/core/module-text.h
index e718a40..2b6827f 100644
--- a/core/module-text.h
+++ b/core/module-text.h
@@ -33,6 +33,6 @@ size_t pure_text_step(const PureDraft *, PureVersion, size_t position, double co
size_t pure_text_line_step(const PureDraft *, PureVersion, size_t position, double count);
size_t pure_text_line_start(const PureDraft *, PureVersion, size_t position);
size_t pure_text_line_end(const PureDraft *, PureVersion, size_t position);
-/* Scalars (unit 0) or line number counting from 1 (unit 1) before position. */
+/* Scalars (unit 0) or line breaks (unit 1: the line, counting from 0) before position. */
size_t pure_text_count(const PureDraft *, PureVersion, size_t position, unsigned unit);
#endif
diff --git a/core/module.c b/core/module.c
index 5ebecea..55923b2 100644
--- a/core/module.c
+++ b/core/module.c
@@ -37,7 +37,7 @@ enum { READ = IN_VIEW | IN_HANDLER };
* what a position is (A_POSITION: 0 a number, 1 an i64), what CONCAT joins
* (A_JOINED: 0 texts, 1 bytes), how NUM_TEXT writes (A_FORMAT) or the value
* held by name (A_HELD), or a function (A_FUNCTION: CALL's, and RETURN's own). */
-enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_CONSTANT, A_UNIT,
+enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_CONSTANT, A_IN_TEXT, A_IN_LIST,
A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_TYPE,
A_LOOP, A_BITS, A_POSITION, A_JOINED, A_FORMAT, A_HELD, A_FUNCTION };
/* A fold's block is verified as a column that leaves one bool (count, first)
@@ -51,11 +51,11 @@ enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_CONSTA
* blocks in it are a handler's code. */
enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK, STEPS, STEPS_WALK, FUNCTION };
/* The text or list an opcode works on: its cursor and index operands belong
- * to it, and so does a cursor or index it pushes. O_ANY: no rule. O_OPERAND:
- * it pushes a position in its operand's text or list. O_ARG: the argument.
- * O_REF: the text or list of the argument's cursor or index state. O_LIST:
- * the list of an item argument. */
-enum { O_ANY, O_OPERAND, O_ARG, O_REF, O_LIST };
+ * to it, and so does a cursor or index it pushes. O_ANY: no rule. O_ARG: the
+ * argument. O_REF: the text or list of the argument's cursor or index state.
+ * O_LIST: the argument's bits 0-15 (an item argument's list, an anchor
+ * instruction's text or list). */
+enum { O_ANY, O_ARG, O_REF, O_LIST };
static const unsigned char magic[8] = {'P','U','R','E','M','O','D','7'};
/* Operand types popped (bottom to top), the type pushed, where allowed, what
@@ -65,7 +65,7 @@ static const unsigned char magic[8] = {'P','U','R','E','M','O','D','7'};
#define LOGIC(pops) {pops, {T_BOOL, T_BOOL}, T_BOOL, READ, A_NONE, O_ANY}
#define I64S(pops) {pops, {T_I64, T_I64}, T_I64, READ, A_NONE, O_ANY}
#define STATE(type) A_STATE + PURE_STATE_##type
-static const struct { unsigned char pops, in[3], out; unsigned short where; unsigned char arg, of; } signature[PURE_OP_COUNT] = {
+static const struct { unsigned char pops, in[3], out; unsigned short where; unsigned char arg, of; } signature[PURE_OP_KEPT_GET] = {
[PURE_OP_CONST] = {0, {0}, T_NUM, READ, A_TYPE, O_ANY}, [PURE_OP_ADD] = NUMS(2), [PURE_OP_SUB] = NUMS(2),
[PURE_OP_MUL] = NUMS(2), [PURE_OP_DIV] = NUMS(2), [PURE_OP_NEG] = NUMS(1), [PURE_OP_LT] = COMPARE,
[PURE_OP_LE] = COMPARE, [PURE_OP_GT] = COMPARE, [PURE_OP_GE] = COMPARE, [PURE_OP_EQ] = COMPARE,
@@ -91,7 +91,7 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_CUR_LOAD] = {0, {0}, T_CURSOR, READ, STATE(CURSOR), O_REF},
[PURE_OP_CUR_START] = {0, {0}, T_CURSOR, READ, STATE(TEXT), O_ARG},
[PURE_OP_CUR_END] = {0, {0}, T_CURSOR, READ, STATE(TEXT), O_ARG},
- [PURE_OP_CUR_MOVE] = {2, {T_CURSOR, T_NUM}, T_CURSOR, READ, A_UNIT, O_OPERAND},
+ [PURE_OP_CUR_MOVE] = {2, {T_CURSOR, T_NUM}, T_CURSOR, READ, A_IN_TEXT, O_LIST},
[PURE_OP_CUR_STORE] = {1, {T_CURSOR}, 0, IN_HANDLER, STATE(CURSOR), O_REF},
[PURE_OP_TEXT_INSERT] = {2, {T_CURSOR, T_TEXT}, 0, IN_HANDLER, STATE(TEXT), O_ARG},
[PURE_OP_TEXT_REMOVE] = {2, {T_CURSOR, T_CURSOR}, 0, IN_HANDLER, STATE(TEXT), O_ARG},
@@ -99,7 +99,8 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_TEXT_SET] = {1, {T_TEXT}, 0, IN_HANDLER, STATE(TEXT), O_ANY},
[PURE_OP_TEXT_FROM] = {1, {T_BYTES}, T_TEXT, READ, A_NONE, O_ANY},
[PURE_OP_BYTES_FROM] = {1, {T_TEXT}, T_BYTES, READ, A_NONE, O_ANY},
- [PURE_OP_CUR_COUNT] = {1, {T_CURSOR}, T_NUM, READ, A_UNIT, O_ANY},
+ [PURE_OP_CUR_COUNT] = {1, {T_CURSOR}, T_NUM, READ, A_IN_TEXT, O_LIST},
+ [PURE_OP_CUR_AT] = {1, {T_NUM}, T_CURSOR, READ, A_IN_TEXT, O_LIST},
/* Record operations also take as many values as the list's record has fields. */
[PURE_OP_LIST_COUNT] = {0, {0}, T_NUM, READ, STATE(LIST), O_ANY},
[PURE_OP_LIST_APPEND] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ANY},
@@ -109,10 +110,10 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_ITEM_SET] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, A_ITEM, O_LIST},
[PURE_OP_INDEX_LOAD] = {0, {0}, T_INDEX, READ, STATE(INDEX), O_REF},
[PURE_OP_INDEX_STORE] = {1, {T_INDEX}, 0, IN_HANDLER, STATE(INDEX), O_REF},
- [PURE_OP_INDEX_MOVE] = {2, {T_INDEX, T_NUM}, T_INDEX, READ, A_NONE, O_OPERAND},
+ [PURE_OP_INDEX_MOVE] = {2, {T_INDEX, T_NUM}, T_INDEX, READ, A_IN_LIST, O_LIST},
[PURE_OP_INDEX_FIRST] = {0, {0}, T_INDEX, READ, STATE(LIST), O_ARG},
[PURE_OP_INDEX_LAST] = {0, {0}, T_INDEX, READ, STATE(LIST), O_ARG},
- [PURE_OP_INDEX_NUM] = {1, {T_INDEX}, T_NUM, READ, A_NONE, O_ANY},
+ [PURE_OP_INDEX_NUM] = {1, {T_INDEX}, T_NUM, READ, A_IN_LIST, O_LIST},
[PURE_OP_ROW] = {0, {0}, T_INDEX, IN_ROW, A_ROW, O_ARG},
/* In a fold that runs in a column, the column's row (the fold's own item is ROW's). */
[PURE_OP_OUTER_ROW] = {0, {0}, T_INDEX, IN_OUTER, A_OUTER, O_ARG},
@@ -356,8 +357,7 @@ static uint32_t nested_record(const PureModule *m, uint32_t field) {
static bool kept_list(const PureModule *m, uint32_t list) { return !(m->state_flags[list] & PURE_LIST_ROWS >> 8); }
/* The steps of a kept list's lists, apart from the instructions' own (027's stage 3): the load gives ITEM_GET and
* ITEM_SET of a kept list's list field, and LIST_APPEND, LIST_INSERT and APPEND_IN of a kept list whose record holds
- * lists, these numbers past the module's, so the others' steps stay as they were. */
-enum { OP_KEPT_GET = PURE_OP_COUNT, OP_KEPT_SET, OP_KEPT_APPEND, OP_KEPT_INSERT, OP_KEPT_APPEND_IN };
+ * lists, the runtime's own instructions (PURE_OP_KEPT_*, module-run.h), so the others' steps stay as they were. */
/* An event's list, and one bound by LIST_BIND (a register: never changed by code). */
static bool event_list_state(const PureModule *m, uint32_t list) {
@@ -424,7 +424,9 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg
return state_of(m, list_of(arg), PURE_STATE_LIST) && item_field(arg) < m->record[m->state_ref[list_of(arg)]].count;
case A_FIELD: return arg < shape->field_count && m->field[shape->field_start + arg].type != PURE_FIELD_ROWS;
case A_CONSTANT: return arg < m->constants;
- case A_UNIT: return arg <= PURE_UNIT_LINE;
+ case A_IN_TEXT: /* 027's 4c: the text an anchor is in, and a unit */
+ return state_of(m, arg & 0xFFFF, PURE_STATE_TEXT) && arg >> PURE_ANCHOR_UNIT <= PURE_UNIT_LINE;
+ case A_IN_LIST: return state_of(m, arg, PURE_STATE_LIST); /* the list, its unit an item (0) */
case A_ROW: return (int)arg == row;
case A_OUTER: return outer >= 0 && (int)arg == outer;
case A_FOLD: case A_EACH: { /* a slice's list is keyed by a number */
@@ -541,7 +543,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (!reachable) REFUSE("unreachable instruction");
if (m->height) m->height[pc] = (uint8_t)height; /* the compiled code's slots (021) */
const PureOp *op = &m->code[pc];
- if (op->op >= PURE_OP_COUNT) REFUSE("unknown opcode");
+ if (op->op >= PURE_OP_KEPT_GET) REFUSE("unknown opcode"); /* (a module's own were refused as it loaded) */
unsigned allowed = signature[op->op].where, kind = signature[op->op].arg, of = signature[op->op].of;
if (!(allowed & where)) {
if (allowed & IN_FUNCTION) REFUSE("return outside a function");
@@ -726,7 +728,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
}
if (out) {
if (height == PURE_MODULE_STACK) REFUSE("stack overflow");
- owners[height] = (unsigned char)(out != T_CURSOR && out != T_INDEX ? 0 : of == O_OPERAND ? owners[height] : owner);
+ owners[height] = (unsigned char)(out != T_CURSOR && out != T_INDEX ? 0 : owner);
if (out == T_LIST) /* the record of its items */
owners[height] = (unsigned char)(op->op == PURE_OP_LIST_VALUE ? m->state_ref[arg] : op->op == PURE_OP_LOCAL_LOAD ? arg >> 16 :
nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg)));
@@ -1575,6 +1577,26 @@ void pure_module_release(PureModule *m) {
m->constant_data = NULL;
}
+/* A module's instruction as the runtime holds it: an anchor instruction (027's 4c) is a cursor's when its anchor is in
+ * a text and an index's when in a list (the state's, or the text or list it names), else a cursor's that the verifier
+ * refuses (what it names is no text); any other is itself. */
+static uint8_t anchor_step(const PureModule *m, uint8_t op, uint32_t arg) {
+ if (op == PURE_OP_ANCHOR_LOAD || op == PURE_OP_ANCHOR_STORE) {
+ bool index = arg < m->states && m->state_type[arg] == PURE_STATE_INDEX;
+ return (uint8_t)(op == PURE_OP_ANCHOR_LOAD ? (index ? PURE_OP_INDEX_LOAD : PURE_OP_CUR_LOAD)
+ : (index ? PURE_OP_INDEX_STORE : PURE_OP_CUR_STORE));
+ }
+ bool list = (arg & 0xFFFF) < m->states && m->state_type[arg & 0xFFFF] == PURE_STATE_LIST;
+ switch (op) {
+ case PURE_OP_ANCHOR_START: return list ? PURE_OP_INDEX_FIRST : PURE_OP_CUR_START;
+ case PURE_OP_ANCHOR_END: return list ? PURE_OP_INDEX_LAST : PURE_OP_CUR_END;
+ case PURE_OP_ANCHOR_MOVE: return list ? PURE_OP_INDEX_MOVE : PURE_OP_CUR_MOVE;
+ case PURE_OP_ANCHOR_COUNT: return list ? PURE_OP_INDEX_NUM : PURE_OP_CUR_COUNT;
+ case PURE_OP_ANCHOR_AT: return list ? PURE_OP_INDEX_AT : PURE_OP_CUR_AT;
+ default: return op;
+ }
+}
+
PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule *out,
char *error, size_t error_size) {
const unsigned char *p = bytes;
@@ -1641,10 +1663,16 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
const unsigned char *at = p + PURE_MODULE_HEADER;
const unsigned char *text = p + size - constant_bytes - strings;
if (constant_bytes) memcpy(out->constant_data, p + size - constant_bytes, constant_bytes);
+ /* A module's state types as the runtime holds them: an anchor (027's 4c) a cursor here, an index once it is found to
+ * be in a list (below). */
+ static const uint8_t held_as[] = {[PURE_MODULE_NUM] = PURE_STATE_NUM, [PURE_MODULE_TEXT] = PURE_STATE_TEXT,
+ [PURE_MODULE_ANCHOR] = PURE_STATE_CURSOR, [PURE_MODULE_LIST] = PURE_STATE_LIST,
+ [PURE_MODULE_BOOL] = PURE_STATE_BOOL, [PURE_MODULE_I64] = PURE_STATE_I64};
+ const unsigned char *states_at = at;
for (uint32_t i = 0; i < out->states; ++i, at += STATE_BYTES) {
uint32_t type = read32(at + 4);
- out->state_type[i] = (uint8_t)type;
- if ((type & ~(uint32_t)PURE_STATE_TRANSIENT) > PURE_STATE_I64) REFUSE("state type", i);
+ if ((type & ~(uint32_t)PURE_STATE_TRANSIENT) > PURE_MODULE_I64) REFUSE("state type", i);
+ out->state_type[i] = held_as[type & 0xFF];
if (type & PURE_STATE_TRANSIENT) out->transient |= UINT32_C(1) << i;
/* A number's, a bool's or an i64's value is its 8 bytes; others have flags:
* a mark (cursor), a keyed list's key field + 1, or a list of a message's rows. */
@@ -1675,6 +1703,14 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->state_key[i] = (uint8_t)key;
}
}
+ /* An anchor in a list is an index (a mark is a text's alone). */
+ for (uint32_t i = 0; i < out->states; ++i) {
+ uint32_t ref = out->state_ref[i];
+ if (out->state_type[i] != PURE_STATE_CURSOR || ref >= out->states ||
+ (read32(states_at + (size_t)ref * STATE_BYTES + 4) & 0xFF) != PURE_MODULE_LIST) continue;
+ if (out->state_flags[i]) REFUSE("state reference", i);
+ out->state_type[i] = PURE_STATE_INDEX;
+ }
uint32_t next_field = 0;
const unsigned char *fields = at + (size_t)out->inputs * INPUT_BYTES;
for (uint32_t i = 0; i < out->inputs; ++i, at += INPUT_BYTES) {
@@ -1864,7 +1900,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->calls = out->functions > 0;
for (uint32_t i = 0; i < out->code_count; ++i, at += OP_BYTES) {
if (at[1] || at[2] || at[3]) REFUSE("instruction padding", i);
- out->code[i] = (PureOp){.op = at[0], .arg = read32(at + 4), .value = read_double(at + 8)};
+ if (at[0] >= PURE_OP_COUNT) REFUSE("unknown opcode", i);
+ out->code[i] = (PureOp){.op = anchor_step(out, at[0], read32(at + 4)), .arg = read32(at + 4), .value = read_double(at + 8)};
}
/* States, records, inputs and views share one namespace. */
const char *names[PURE_MODULE_STATES + PURE_MODULE_RECORDS + PURE_MODULE_INPUTS + PURE_MODULE_VIEWS];
@@ -2045,9 +2082,9 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
((op->op == PURE_OP_ITEM_GET || op->op == PURE_OP_ITEM_SET) &&
out->record_field_type[out->record[out->state_ref[list]].first + item_field(op->arg)] != PURE_RECORD_LIST))
continue;
- op->op = (uint8_t)(op->op == PURE_OP_ITEM_GET ? OP_KEPT_GET : op->op == PURE_OP_ITEM_SET ? OP_KEPT_SET :
- op->op == PURE_OP_LIST_APPEND ? OP_KEPT_APPEND : op->op == PURE_OP_LIST_INSERT ? OP_KEPT_INSERT :
- OP_KEPT_APPEND_IN);
+ op->op = (uint8_t)(op->op == PURE_OP_ITEM_GET ? PURE_OP_KEPT_GET : op->op == PURE_OP_ITEM_SET ? PURE_OP_KEPT_SET :
+ op->op == PURE_OP_LIST_APPEND ? PURE_OP_KEPT_APPEND : op->op == PURE_OP_LIST_INSERT ? PURE_OP_KEPT_INSERT :
+ PURE_OP_KEPT_APPEND_IN);
}
out->runner = out->calls ? 2 : out->batches ? 1 : 0;
out->jit = pure_jit_make(out); /* none: the executor runs it all */
@@ -2336,6 +2373,19 @@ static void move_indices(const PureModule *m, PureValue *values, uint32_t list,
+/* The cursor count units (arg's: a character or a line) from the start of the text arg names, in *at; false past the
+ * text (027's 4c, AT). */
+static __attribute__((cold, noinline)) bool cursor_at(const PureApp *app, uint32_t arg, double count, double *at) {
+ PureDraft *text = app->texts[arg & 0xFFFF];
+ PureVersion version = pure_draft_version(text);
+ unsigned unit = arg >> PURE_ANCHOR_UNIT;
+ if (count < 0) return false;
+ size_t position = unit == PURE_UNIT_CHAR ? pure_text_step(text, version, 0, count) : pure_text_line_step(text, version, 0, count);
+ if ((double)pure_text_count(text, version, position, unit) != count) return false;
+ *at = (double)position;
+ return true;
+}
+
/* x / 2^n rounded down, whatever the sign (027's RIGHT). */
static inline int64_t floor_shift(int64_t x, unsigned n) { return x >= 0 ? x >> n : ~(~x >> n); }
@@ -2636,9 +2686,9 @@ static __attribute__((cold, noinline)) int append_kept(PureApp *app, PureArena *
/* How many values a kept step takes off the stack (net: KEPT_GET's value takes its index's place). */
static size_t kept_pops(const PureModule *m, const PureOp *op) {
switch (op->op) {
- case OP_KEPT_GET: return 0;
- case OP_KEPT_SET: return 2;
- case OP_KEPT_APPEND: case OP_KEPT_INSERT: return m->record[m->state_ref[op->arg]].count + (op->op == OP_KEPT_INSERT);
+ case PURE_OP_KEPT_GET: return 0;
+ case PURE_OP_KEPT_SET: return 2;
+ case PURE_OP_KEPT_APPEND: case PURE_OP_KEPT_INSERT: return m->record[m->state_ref[op->arg]].count + (op->op == PURE_OP_KEPT_INSERT);
default: return m->record[nested_record(m, m->record[m->state_ref[list_of(op->arg)]].first + item_field(op->arg))].count + 1u;
}
}
@@ -2651,12 +2701,12 @@ static __attribute__((cold, noinline)) int kept_step(PureApp *app, PureArena *ar
#define KEPT_REFUSED(r) return (int)(r) << 2 | 2
#define KEPT_STOPPED(r) return (int)(r) << 2 | 3
const PureModule *m = app->module;
- uint32_t arg = op->arg, list = op->op == OP_KEPT_APPEND || op->op == OP_KEPT_INSERT ? arg : list_of(arg), field = item_field(arg);
+ uint32_t arg = op->arg, list = op->op == PURE_OP_KEPT_APPEND || op->op == PURE_OP_KEPT_INSERT ? arg : list_of(arg), field = item_field(arg);
PureList *items = app->lists[list];
size_t mark = arena->used;
PureResult status;
switch (op->op) {
- case OP_KEPT_GET: { /* the list in an item's field, read into one of the event's lists */
+ case PURE_OP_KEPT_GET: { /* the list in an item's field, read into one of the event's lists */
size_t at = (size_t)stack[sp - 1].num;
if (at >= pure_list_count(items)) KEPT_FAULT(PURE_FAULT_NOT_FOUND);
if ((status = kept_value(app, list, field, pure_list_get(items, at, field), &stack[sp - 1].i64)) == PURE_BUDGET)
@@ -2664,7 +2714,7 @@ static __attribute__((cold, noinline)) int kept_step(PureApp *app, PureArena *ar
if (status != PURE_OK) KEPT_STOPPED(status);
return 0;
}
- case OP_KEPT_SET: { /* an item's field given a list, written in its stored form */
+ case PURE_OP_KEPT_SET: { /* an item's field given a list, written in its stored form */
size_t at = (size_t)stack[sp - 2].num;
Held before = list_held(app, items);
PureCell cell;
@@ -2678,10 +2728,10 @@ static __attribute__((cold, noinline)) int kept_step(PureApp *app, PureArena *ar
if (!grows(app, before, list_held(app, items))) KEPT_FAULT(PURE_FAULT_MEMORY);
return 0;
}
- case OP_KEPT_APPEND: case OP_KEPT_INSERT: { /* an item whose lists go in their stored form */
+ case PURE_OP_KEPT_APPEND: case PURE_OP_KEPT_INSERT: { /* an item whose lists go in their stored form */
const PureRecord *record = &m->record[m->state_ref[list]];
size_t base = sp - record->count, count = pure_list_count(items), at = count;
- if (op->op == OP_KEPT_INSERT && (at = (size_t)stack[base - 1].num) > count) KEPT_FAULT(PURE_FAULT_NOT_FOUND);
+ if (op->op == PURE_OP_KEPT_INSERT && (at = (size_t)stack[base - 1].num) > count) KEPT_FAULT(PURE_FAULT_NOT_FOUND);
PureCell cells[PURE_RECORD_FIELDS];
if ((status = kept_cells(app, arena, m->state_ref[list], stack + base, cells)) != PURE_OK) KEPT_STOPPED(status);
Held before = list_held(app, items);
@@ -3785,13 +3835,20 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
}
PureDraft *text = app->texts[at->bytes.owner];
PureVersion version = pure_draft_version(text);
- at->num = (double)(arg == PURE_UNIT_CHAR ? pure_text_step(text, version, (size_t)at->num, count)
- : pure_text_line_step(text, version, (size_t)at->num, count));
+ at->num = (double)(arg >> PURE_ANCHOR_UNIT == PURE_UNIT_CHAR ? pure_text_step(text, version, (size_t)at->num, count)
+ : pure_text_line_step(text, version, (size_t)at->num, count));
NEXT;
}
case PURE_OP_CUR_COUNT: {
PureDraft *text = app->texts[top->bytes.owner];
- top->num = (double)pure_text_count(text, pure_draft_version(text), (size_t)top->num, arg);
+ top->num = (double)pure_text_count(text, pure_draft_version(text), (size_t)top->num, arg >> PURE_ANCHOR_UNIT);
+ NEXT;
+ }
+ case PURE_OP_CUR_AT: {
+ double count = top->num;
+ if (count != floor(count)) { kind = PURE_FAULT_LIMIT; FAILED; }
+ if (!cursor_at(app, arg, count, &top->num)) { kind = PURE_FAULT_NOT_FOUND; FAILED; }
+ top->bytes.owner = (uint16_t)(arg & 0xFFFF);
NEXT;
}
case PURE_OP_TEXT_INSERT: case PURE_OP_TEXT_SET: {
@@ -4220,7 +4277,7 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
if (!grows(app, before, (Held){0, pure_list_size(in)})) { kind = PURE_FAULT_MEMORY; FAILED; }
NEXT;
}
- case OP_KEPT_GET: case OP_KEPT_SET: case OP_KEPT_APPEND: case OP_KEPT_INSERT: case OP_KEPT_APPEND_IN: {
+ case PURE_OP_KEPT_GET: case PURE_OP_KEPT_SET: case PURE_OP_KEPT_APPEND: case PURE_OP_KEPT_INSERT: case PURE_OP_KEPT_APPEND_IN: {
int how = kept_step(app, arena, values, op, stack, sp); /* a kept list's lists (027's stage 3): out of the way */
sp -= kept_pops(m, op);
if (how & 3) {
diff --git a/core/module.h b/core/module.h
index d155b41..16c2a70 100644
--- a/core/module.h
+++ b/core/module.h
@@ -31,24 +31,34 @@ typedef enum {
PURE_OP_REPLY, PURE_OP_REQUIRE,
PURE_OP_TEXT_CONST, PURE_OP_TEXT_LOAD, PURE_OP_TEXT_INPUT, PURE_OP_NUM_TEXT,
PURE_OP_CONCAT, PURE_OP_CHARS, PURE_OP_LINES, PURE_OP_TEXT_BYTES,
- PURE_OP_CUR_LOAD, PURE_OP_CUR_START, PURE_OP_CUR_END, PURE_OP_CUR_MOVE,
- PURE_OP_CUR_STORE, PURE_OP_TEXT_INSERT, PURE_OP_TEXT_REMOVE, PURE_OP_TEXT_LINE,
- PURE_OP_TEXT_SET, PURE_OP_TEXT_FROM, PURE_OP_BYTES_FROM, PURE_OP_CUR_COUNT,
+ 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,
+ PURE_OP_TEXT_SET, PURE_OP_TEXT_FROM, PURE_OP_BYTES_FROM, PURE_OP_ANCHOR_COUNT,
PURE_OP_LIST_COUNT, PURE_OP_LIST_APPEND, PURE_OP_LIST_INSERT, PURE_OP_LIST_REMOVE,
- PURE_OP_ITEM_GET, PURE_OP_ITEM_SET, PURE_OP_INDEX_LOAD, PURE_OP_INDEX_STORE,
- PURE_OP_INDEX_MOVE, PURE_OP_INDEX_FIRST, PURE_OP_INDEX_LAST, PURE_OP_INDEX_NUM, PURE_OP_ROW,
+ PURE_OP_ITEM_GET, PURE_OP_ITEM_SET, PURE_OP_ROW,
PURE_OP_PICK, PURE_OP_SLIDE, PURE_OP_FOLD, PURE_OP_TEXT_COMPARE, PURE_OP_LIST_FIND, PURE_OP_LIST_HAS,
PURE_OP_NOW, PURE_OP_FLOOR, PURE_OP_MOD, PURE_OP_SEND, PURE_OP_OUTER_ROW, PURE_OP_EACH,
PURE_OP_LOCAL_LOAD, PURE_OP_LOCAL_STORE,
PURE_OP_I64_ADD, PURE_OP_I64_SUB, PURE_OP_I64_MUL, PURE_OP_I64_DIV, PURE_OP_I64_MOD, PURE_OP_I64_NEG,
PURE_OP_I64_COMPARE, PURE_OP_I64_MIN, PURE_OP_I64_MAX, PURE_OP_I64_FROM, PURE_OP_NUM_FROM,
PURE_OP_LOOP, PURE_OP_BREAK,
- PURE_OP_I64_BITS, PURE_OP_BYTE_AT, PURE_OP_BYTES_OF, PURE_OP_INDEX_AT,
+ PURE_OP_I64_BITS, PURE_OP_BYTE_AT, PURE_OP_BYTES_OF, PURE_OP_ANCHOR_AT,
PURE_OP_BYTES_SLICE, PURE_OP_BYTE, PURE_OP_BYTES_LOAD, PURE_OP_BYTES_STORE,
PURE_OP_LIST_VALUE, PURE_OP_LIST_BIND, PURE_OP_APPEND_IN, PURE_OP_NUM_PARSE,
PURE_OP_CALL, PURE_OP_RETURN, PURE_OP_CHAR_AT, PURE_OP_CHAR,
PURE_OP_COUNT
} PureOpcode;
+/* 027's 4c: an anchor is a place in a text or a list that follows its edits. A text's (a cursor) is a gap between
+ * characters: what is inserted at it goes before it (a mark: after it), and in a removed range it goes to the range's
+ * start. A list's (an index) is the gap before an item, naming it: what is inserted at it goes before it (it follows its
+ * item); it names the next item when its item goes, the last one past the end (0 in an empty list). The instructions
+ * name what their anchor is in: LOAD and STORE an anchor state; START and END a text (its start and end) or a list (its
+ * first and last item); MOVE, COUNT and AT the text or list in bits 0-15 and a unit in bits 16-23 (a text's: 0 a
+ * character, 1 a line; a list's: 0, an item). MOVE moves by a whole number of units (else LIMIT), stopping at the ends;
+ * COUNT gives the units before it (lines from 0); AT gives the anchor a whole number of units from the start (else
+ * LIMIT), NOT_FOUND past the end. The load holds each as the runtime's own instruction for a cursor or an index
+ * (module-run.h). */
+enum { PURE_ANCHOR_UNIT = 16 };
/* A bool is its own type (docs/design/016-bool-and-i64.md), held as a
* number 0 or 1 while it runs: comparisons (LT to NE, TEXT_COMPARE) and
* LIST_HAS push one, AND, OR and NOT work on them, JZ and REQUIRE take one,
@@ -227,10 +237,14 @@ static inline bool pure_field_fixed(uint8_t type) {
static inline uint32_t pure_field_size(uint8_t type) {
return type == PURE_FIELD_NUMBER || type == PURE_FIELD_I64 ? 8 : type == PURE_FIELD_BOOL ? 1 : 4;
}
+/* A loaded module's states: a module's anchor state (027's 4c: PURE_MODULE_ANCHOR, type 2) is a cursor when it is in a
+ * text and an index when it is in a list; the rest are the module's types, a bool's and an i64's 4 and 5 in a module. */
typedef enum {
PURE_STATE_NUM = 0, PURE_STATE_TEXT = 1, PURE_STATE_CURSOR = 2, PURE_STATE_LIST = 3, PURE_STATE_INDEX = 4,
PURE_STATE_BOOL = 5, PURE_STATE_I64 = 6
} PureStateType;
+/* A state's type in a module: a number, a text, an anchor, a list, a bool or an i64. */
+enum { PURE_MODULE_NUM, PURE_MODULE_TEXT, PURE_MODULE_ANCHOR, PURE_MODULE_LIST, PURE_MODULE_BOOL, PURE_MODULE_I64 };
/* A number's, a bool's or an i64's state as a list cell's kind (PURE_CELL_...,
* for pure_cell_as), or 0xFF for any other state. */
static inline uint8_t pure_value_kind(uint8_t state_type) {
diff --git a/core/test-module.c b/core/test-module.c
index 2696e28..3c221e4 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -89,6 +89,30 @@ static unsigned char *put_double(unsigned char *p, double value) {
}
/* Test-side encoder for format v6; the toolchain has its own. */
+/* The tests write states and code as the runtime holds them (a cursor, an index, the steps of each: module-run.h); a
+ * module has their anchor (027's 4c), its type 2, and its instructions, which name the text or list (bits 0-15 of a
+ * MOVE's and a COUNT's argument: IN), so a test gives that where a step does not name it. */
+#define IN(target, unit) ((uint32_t)(target) | (uint32_t)(unit) << PURE_ANCHOR_UNIT)
+static uint32_t module_type(uint32_t type) {
+ static const uint8_t types[] = {[PURE_STATE_NUM] = PURE_MODULE_NUM, [PURE_STATE_TEXT] = PURE_MODULE_TEXT,
+ [PURE_STATE_CURSOR] = PURE_MODULE_ANCHOR, [PURE_STATE_LIST] = PURE_MODULE_LIST,
+ [PURE_STATE_INDEX] = PURE_MODULE_ANCHOR, [PURE_STATE_BOOL] = PURE_MODULE_BOOL,
+ [PURE_STATE_I64] = PURE_MODULE_I64};
+ return type < sizeof(types) ? types[type] : type;
+}
+static uint8_t module_op(uint8_t op) {
+ switch (op) {
+ case PURE_OP_CUR_LOAD: case PURE_OP_INDEX_LOAD: return PURE_OP_ANCHOR_LOAD;
+ case PURE_OP_CUR_STORE: case PURE_OP_INDEX_STORE: return PURE_OP_ANCHOR_STORE;
+ case PURE_OP_CUR_START: case PURE_OP_INDEX_FIRST: return PURE_OP_ANCHOR_START;
+ case PURE_OP_CUR_END: case PURE_OP_INDEX_LAST: return PURE_OP_ANCHOR_END;
+ case PURE_OP_CUR_MOVE: case PURE_OP_INDEX_MOVE: return PURE_OP_ANCHOR_MOVE;
+ case PURE_OP_CUR_COUNT: case PURE_OP_INDEX_NUM: return PURE_OP_ANCHOR_COUNT;
+ case PURE_OP_CUR_AT: case PURE_OP_INDEX_AT: return PURE_OP_ANCHOR_AT;
+ default: return op;
+ }
+}
+
static size_t build(unsigned char *out, Spec spec) {
unsigned char strings[4096], blob[4096];
size_t used = 0, blob_used = 0;
@@ -139,7 +163,7 @@ static size_t build(unsigned char *out, Spec spec) {
for (size_t i = 0; i < spec.ns; ++i) { p = put32(p, offset[n++]); p = put32(p, 0); p = put_double(p, spec.s[i].initial); }
for (size_t i = 0; i < spec.nt; ++i) {
p = put32(p, offset[n++]);
- p = put32(p, (spec.t[i].type & 0xFF) | (spec.t[i].type & TR ? PURE_STATE_TRANSIENT : 0));
+ p = put32(p, module_type((spec.t[i].type & 0xFF)) | (spec.t[i].type & TR ? PURE_STATE_TRANSIENT : 0));
if ((spec.t[i].type & 0xFF) == PURE_STATE_BOOL) { p = put_double(p, spec.t[i].ref); continue; }
if ((spec.t[i].type & 0xFF) == PURE_STATE_I64) {
int64_t initial = (int32_t)spec.t[i].ref;
@@ -220,7 +244,7 @@ static size_t build(unsigned char *out, Spec spec) {
*p++ = (unsigned char)spec_fn[i].records[k]; *p++ = (unsigned char)(spec_fn[i].records[k] >> 8);
}
for (size_t i = 0; i < spec.no; ++i) {
- *p++ = spec.o[i].op; *p++ = 0; *p++ = 0; *p++ = 0;
+ *p++ = module_op(spec.o[i].op); *p++ = 0; *p++ = 0; *p++ = 0;
p = put32(p, spec.o[i].arg);
p = put_double(p, spec.o[i].value);
}
@@ -368,7 +392,7 @@ static void rejections(void) {
static const O nan_const[] = {{C, 0, NAN}, {ST, 0, 0}};
static const O noncanon[] = {{C, 0, 1}, {NEG, 1, 0}, {ST, 0, 0}};
static const O unreachable[] = {{JMP, 2, 0}, {C, 0, 1}, {C, 0, 1}, {ST, 0, 0}};
- static const O bad_op[] = {{PURE_OP_COUNT, 0, 0}, {ST, 0, 0}}; /* past the last opcode */
+ static const O bad_op[] = {{PURE_OP_KEPT_GET, 0, 0}, {ST, 0, 0}}; /* past a module's opcodes: the runtime's own */
static const H h_all[] = {{0, 0, 1}};
static const H h_two[] = {{0, 0, 2}};
static const H h_five[] = {{0, 0, 5}};
@@ -659,16 +683,16 @@ static void texts(void) {
};
static const O code[] = {
{CUL, 2, 0}, {TI, 0, 0}, {TINS, 1, 0}, {L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, /* 0 typed */
- {CUL, 2, 0}, {C, 0, -1}, {CM, 0, 0}, {CS, 2, 0}, /* 7 left */
- {CUL, 2, 0}, {C, 0, 1}, {CM, 0, 0}, {CS, 2, 0}, /* 11 right */
- {CUL, 2, 0}, {C, 0, -1}, {CM, 1, 0}, {CS, 2, 0}, /* 15 up */
- {CUL, 2, 0}, {C, 0, 1}, {CM, 1, 0}, {CS, 2, 0}, /* 19 down */
- {CUL, 2, 0}, {C, 0, -1}, {CM, 0, 0}, {CUL, 2, 0}, {TREM, 1, 0}, /* 23 back */
+ {CUL, 2, 0}, {C, 0, -1}, {CM, IN(1, 0), 0}, {CS, 2, 0}, /* 7 left */
+ {CUL, 2, 0}, {C, 0, 1}, {CM, IN(1, 0), 0}, {CS, 2, 0}, /* 11 right */
+ {CUL, 2, 0}, {C, 0, -1}, {CM, IN(1, 1), 0}, {CS, 2, 0}, /* 15 up */
+ {CUL, 2, 0}, {C, 0, 1}, {CM, IN(1, 1), 0}, {CS, 2, 0}, /* 19 down */
+ {CUL, 2, 0}, {C, 0, -1}, {CM, IN(1, 0), 0}, {CUL, 2, 0}, {TREM, 1, 0}, /* 23 back */
{CST, 1, 0}, {CS, 2, 0}, /* 28 home */
{CUL, 2, 0}, {TK, 2, 0}, {TINS, 1, 0}, {C, 0, 1}, {C, 0, 0}, {DIV, 0, 0}, {ST, 0, 0}, /* 30 bad */
{TL, 1, 0}, {CHR, 0, 0}, /* 37 view chars */
- {CUL, 2, 0}, {CC, 0, 0}, /* 39 view offset */
- {CUL, 2, 0}, {CC, 1, 0}, /* 41 view line_no */
+ {CUL, 2, 0}, {CC, IN(1, 0), 0}, /* 39 view offset */
+ {CUL, 2, 0}, {CC, IN(1, 1), 0}, /* 41 view line_no */
{CUL, 2, 0}, {TLN, 1, 0}, /* 43 view line */
{TL, 3, 0}, {TK, 3, 0}, {CAT, 0, 0}, {TL, 1, 0}, {CHR, 0, 0}, {NT, 0, 0}, {CAT, 0, 0},
{TK, 4, 0}, {CAT, 0, 0}, /* 45 view header */
@@ -687,7 +711,7 @@ static void texts(void) {
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);
CHECK(app.values[2].num == 7); /* stops at the end */
- CHECK(number_view(&app, 0) == 5 && number_view(&app, 2) == 2 && !strcmp(view_text(&app, 3), "bc"));
+ 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 */
const char *han = "\xed\x95\x9c";
CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)han, 3}) == PURE_OK);
@@ -1973,14 +1997,14 @@ static void lists(void) {
{TI, 0, 0}, {C, 1, 0}, {LA, 1, 0}, {XE, 1, 0}, {XS, 2, 0}, {L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, /* 0 typed */
{XL, 2, 0}, {TK, 1, 0}, {C, 1, 0}, {LI, 1, 0}, /* 9 ins */
{XL, 2, 0}, {LR, 1, 0}, /* 13 del */
- {XL, 2, 0}, {C, 0, -1}, {XM, 0, 0}, {XS, 2, 0}, /* 15 up */
- {XL, 2, 0}, {C, 0, 1}, {XM, 0, 0}, {XS, 2, 0}, /* 19 down */
+ {XL, 2, 0}, {C, 0, -1}, {XM, 1, 0}, {XS, 2, 0}, /* 15 up */
+ {XL, 2, 0}, {C, 0, 1}, {XM, 1, 0}, {XS, 2, 0}, /* 19 down */
{XL, 2, 0}, {XL, 2, 0}, {IG, DONE, 0}, {NOT, 0, 0}, {IS, DONE, 0}, /* 23 toggle */
{XL, 2, 0}, {XL, 2, 0}, {IG, TITLE, 0}, {TK, 2, 0}, {CAT, 0, 0}, {IS, TITLE, 0}, /* 28 rename */
{XL, 2, 0}, {TK, 2, 0}, {IS, TITLE, 0}, {XL, 2, 0}, {LR, 1, 0}, {TK, 1, 0}, {C, 1, 0}, /* 34 bad */
{LA, 1, 0}, {C, 0, 1}, {C, 0, 0}, {DIV, 0, 0}, {ST, 0, 0},
{LC, 1, 0}, /* 46 view count */
- {XL, 2, 0}, {XN, 0, 0}, /* 47 view position */
+ {XL, 2, 0}, {XN, 1, 0}, /* 47 view position */
{XL, 2, 0}, {IG, DONE, 0}, /* 49 view done */
{XL, 2, 0}, {IG, TITLE, 0}, /* 51 view current */
};
@@ -2182,8 +2206,8 @@ static void tables(void) {
{TK, 3, 0}, /* 10 current */
{ROW, 1, 0}, {IG, TITLE, 0}, /* 18 title */
{ROW, 1, 0}, {IG, DONE, 0}, {JZ, 25, 0}, {TK, 1, 0}, {JMP, 26, 0}, {TK, 2, 0}, /* 20 mark */
- {ROW, 1, 0}, {XN, 0, 0}, /* 26 pos */
- {ROW, 1, 0}, {XN, 0, 0}, {XL, 2, 0}, {XN, 0, 0}, {EQ, 0, 0}, /* 28 here */
+ {ROW, 1, 0}, {XN, 1, 0}, /* 26 pos */
+ {ROW, 1, 0}, {XN, 1, 0}, {XL, 2, 0}, {XN, 1, 0}, {EQ, 0, 0}, /* 28 here */
};
static const H handlers[] = {{1, 0, 5}, {3, 5, 5}};
static const V text_views[] = {{"current", 10, 8}};
@@ -2236,7 +2260,7 @@ static void tables(void) {
static const W over_number[] = {{"board", 2, 0, 0, 0}};
bad = spec; bad.w = over_number; bad.nw = 1;
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "table list"));
- static const O other_row[] = {{ROW, 2, 0}, {XN, 0, 0}};
+ static const O other_row[] = {{ROW, 2, 0}, {XN, 2, 0}};
static const W other_row_column[] = {{"board", 2, 0, 1, 0}, {"pos", 0, 1, 0, 2}};
bad = spec; bad.o = other_row; bad.no = 2; bad.h = NULL; bad.nh = 0; bad.tv = NULL; bad.ntv = 0;
bad.w = other_row_column; bad.nw = 2;
@@ -3119,7 +3143,7 @@ static void gen_index(Gen *g, int list, int depth) {
} else if (choice == 5) {
gen_index(g, list, depth - 1);
gen_num(g, depth - 1);
- put_op(g, PURE_OP_INDEX_MOVE, 0, 0);
+ put_op(g, PURE_OP_INDEX_MOVE, (uint32_t)list, 0);
} else
put_op(g, roll(g, 2) ? PURE_OP_INDEX_FIRST : PURE_OP_INDEX_LAST, (uint32_t)list, 0);
}
@@ -3145,10 +3169,12 @@ static void gen_num(Gen *g, int depth) {
case 1: put_op(g, L, LANE_K, 0); return;
case 2: case 3: gen_field(g, depth, false); return;
case 4: put_op(g, PURE_OP_LIST_COUNT, roll(g, 2) ? LANE_L : LANE_M, 0); return;
- case 5:
- gen_index(g, roll(g, 2) ? LANE_L : LANE_M, depth - 1);
- put_op(g, PURE_OP_INDEX_NUM, 0, 0);
+ case 5: {
+ int list = roll(g, 2) ? LANE_L : LANE_M;
+ gen_index(g, list, depth - 1);
+ put_op(g, PURE_OP_INDEX_NUM, (uint32_t)list, 0);
return;
+ }
case 6: gen_num(g, depth - 1); put_op(g, unary[roll(g, LEN(unary))], 0, 0); return;
case 7: gen_num(g, depth - 1); gen_num(g, depth - 1); gen_num(g, depth - 1); put_op(g, CL, 0, 0); return;
case 8: gen_num(g, depth - 1); put_op(g, PURE_OP_PICK, 0, 0); put_op(g, binary[roll(g, LEN(binary))], 0, 0); return;
@@ -3255,7 +3281,7 @@ static void gen_shaped(Gen *g, int list) {
}
g->fold_list = fold_list, g->outer = fold_outer, g->tree = false;
g->code[at].arg = FOLDING(how, list, g->count - at - 1);
- if (how == PURE_FOLD_FIRST) put_op(g, PURE_OP_INDEX_NUM, 0, 0);
+ if (how == PURE_FOLD_FIRST) put_op(g, PURE_OP_INDEX_NUM, (uint32_t)list, 0);
}
/* A fold over list leaving a number (first's index as one), in a column of L (outer) or not. */
@@ -3269,7 +3295,7 @@ static void gen_fold(Gen *g, int list, int outer) {
gen_bool(g, 3);
g->fold_list = fold_list, g->outer = fold_outer;
g->code[at].arg = FOLDING(how, list, g->count - at - 1);
- if (how == PURE_FOLD_FIRST) put_op(g, PURE_OP_INDEX_NUM, 0, 0);
+ if (how == PURE_FOLD_FIRST) put_op(g, PURE_OP_INDEX_NUM, (uint32_t)list, 0);
}
static bool same_number(bool a_ok, double a, bool b_ok, double b) {
@@ -3872,7 +3898,7 @@ static const O range_code[] = {
{ROW, R_L, 0}, KEYED(0), {L, R_LO, 0}, {GT, 0, 0}, {ROW, R_L, 0}, KEYED(0), {L, R_HI, 0}, {PURE_OP_LE, 0, 0},
{PURE_OP_AND, 0, 0}, {ROW, R_L, 0}, KEYED(2), {C, 0, 0}, {GT, 0, 0}, {PURE_OP_AND, 0, 0},
/* 27 v2 = first(x in l where x.key == lo).position */
- {FOLD, FOLDING(PURE_FOLD_FIRST, R_L, 4), 0}, {ROW, R_L, 0}, KEYED(0), {L, R_LO, 0}, {EQ, 0, 0}, {XN, 0, 0},
+ {FOLD, FOLDING(PURE_FOLD_FIRST, R_L, 4), 0}, {ROW, R_L, 0}, KEYED(0), {L, R_LO, 0}, {EQ, 0, 0}, {XN, R_L, 0},
/* 33 v3 = min(x.b for x in l where hi > x.key && x.key >= lo) */
{FOLD, FOLDING(PURE_FOLD_MIN, R_L, 11), 0}, {ROW, R_L, 0}, KEYED(2),
{L, R_HI, 0}, {ROW, R_L, 0}, KEYED(0), {GT, 0, 0}, {ROW, R_L, 0}, KEYED(0), {L, R_LO, 0}, {PURE_OP_GE, 0, 0},
@@ -4083,7 +4109,7 @@ static const O keyed_code[] = {
{C, 0, 2}, {LA, SCORES, 0}, {C, 0, 0}, {LA, SCORES, 0}, {C, 0, -0.0}, {LA, SCORES, 0}, /* 20 two, zero, minus_zero */
{TI, 0, 0}, {C, 0, 0}, {LA, PEOPLE, 0}, {TI, 0, 0}, {C, 0, 0}, {LA, PEOPLE, 0}, /* 26 twice: always exists */
{LC, PEOPLE, 0}, /* 32 view count */
- {XL, K_AT, 0}, {XN, 0, 0}, /* 33 view position */
+ {XL, K_AT, 0}, {XN, PEOPLE, 0}, /* 33 view position */
{XL, K_AT, 0}, {IG, PEOPLE | K_NAME, 0}, {TK, 1, 0}, {TCMP, PURE_EQUAL, 0}, /* 35 view is_bob */
{XL, K_AT, 0}, {IG, PEOPLE | K_NAME, 0}, {TK, 2, 0}, {TCMP, PURE_LESS, 0}, /* 39 view before_m */
{LC, SCORES, 0}, /* 43 view scores */
@@ -6499,6 +6525,64 @@ static void bytes_fields(void) {
printf("{\"bytes_fields\":{\"items\":2,\"refused\":%zu}}\n", LEN(refused) + 1);
}
+/* 027's 4c: an anchor's instructions name what it is in. AT finds the place so many characters or lines from a text's
+ * start, or the item so many from a list's (past the end: NOT_FOUND, not whole: LIMIT); COUNT gives it back. Refused:
+ * an instruction naming a list on a cursor and a text on an index, a list's unit other than an item, a mark in a list. */
+static void anchors(void) {
+ enum { CAT_ = PURE_OP_CUR_AT, XAT_ = PURE_OP_INDEX_AT, NOTE = 0, TASKS = 1, AT = 2, CARET = 3 };
+ static const char *const constants[] = {"ab\ncd\nef"};
+ static const T states[] = {{"note", PURE_STATE_TEXT, 0}, {"tasks", PURE_STATE_LIST, 0}, {"at", PURE_STATE_INDEX, 1},
+ {"caret", PURE_STATE_CURSOR, 0}};
+ static const RF fields[] = {{"v", 0, 0}};
+ static const R records[] = {{"Task", fields, 1}};
+ static const I inputs[] = {KEY("add", "KeyA")};
+ static const O code[] = {
+ {C, 0, 7}, {LA, TASKS, 0}, /* 0 add: a task */
+ {C, 0, 4}, {CAT_, IN(NOTE, 0), 0}, {CC, IN(NOTE, 0), 0}, /* 2 view chars: 4 */
+ {C, 0, 2}, {CAT_, IN(NOTE, 1), 0}, {CC, IN(NOTE, 0), 0}, /* 5 view line_at: 6 */
+ {C, 0, 2}, {CAT_, IN(NOTE, 1), 0}, {CC, IN(NOTE, 1), 0}, /* 8 view line: 2 */
+ {C, 0, 9}, {CAT_, IN(NOTE, 0), 0}, {CC, IN(NOTE, 0), 0}, /* 11 view past: none */
+ {C, 0, 1.5}, {CAT_, IN(NOTE, 0), 0}, {CC, IN(NOTE, 0), 0}, /* 14 view half: none */
+ {C, 0, 1}, {XAT_, TASKS, 0}, {XN, TASKS, 0}, /* 17 view second: 1 */
+ };
+ static const H handlers[] = {{1, 0, 2}};
+ static const V views[] = {{"chars", 2, 3}, {"line_at", 5, 3}, {"line", 8, 3}, {"past", 11, 3}, {"half", 14, 3},
+ {"second", 17, 3}};
+ Spec spec = {NULL, 0, inputs, 1, views, LEN(views), handlers, 1, code, LEN(code), NULL, 0, states, LEN(states), constants,
+ LEN(constants), NULL, 0, records, 1, NULL, 0};
+ if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "anchors: %s\n", error); abort(); }
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ double value;
+ CHECK(number_view(&app, 0) == 4 && number_view(&app, 1) == 6 && number_view(&app, 2) == 2);
+ 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);
+ CHECK(number_view(&app, 5) == 1);
+ pure_app_stop(&app);
+ const struct { O code[4]; uint32_t count; const char *why; } refused[] = {
+ {{{CUL, CARET, 0}, {C, 0, 1}, {XM, TASKS, 0}, {XN, TASKS, 0}}, 4, "operand type"}, /* a list's move of a cursor */
+ {{{XL, AT, 0}, {CC, IN(NOTE, 0), 0}}, 2, "operand type"}, /* a text's count of an index */
+ {{{C, 0, 0}, {XAT_, IN(TASKS, 1), 0}, {XN, TASKS, 0}}, 3, "no such state"}, /* a list's unit not an item */
+ };
+ for (size_t k = 0; k < LEN(refused); ++k) {
+ V one[] = {{"v", 0, refused[k].count}};
+ Spec bad = {NULL, 0, inputs, 1, one, 1, NULL, 0, refused[k].code, refused[k].count, NULL, 0, states, LEN(states),
+ constants, LEN(constants), NULL, 0, records, 1, NULL, 0};
+ if (load(bad, &module_b) == PURE_OK || !strstr(error, refused[k].why)) {
+ fprintf(stderr, "anchors: refused %zu: %s\n", k, error);
+ abort();
+ }
+ }
+ static const T marked[] = {{"note", PURE_STATE_TEXT, 0}, {"tasks", PURE_STATE_LIST, 0},
+ {"at", PURE_STATE_INDEX | PURE_CURSOR_STAYS << 8, 1}, {"caret", PURE_STATE_CURSOR, 0}};
+ Spec mark = spec;
+ mark.t = marked;
+ CHECK(load(mark, &module_b) != PURE_OK && strstr(error, "state reference"));
+ printf("{\"anchors\":{\"views\":6,\"refused\":%zu}}\n", LEN(refused) + 1);
+}
+
/* A timer's tick sees the time the host set; floor and a floored modulo
* take it apart. now is for handlers only. */
enum { NOW = PURE_OP_NOW, FLOOR = PURE_OP_FLOOR, MOD = PURE_OP_MOD };
@@ -6764,7 +6848,7 @@ static void view_bodies(void) {
{L, N, 0}, {C, 0, 7}, {MOD_, 0, 0}, {LA, XS, 0}, {L, N, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, N, 0},
/* total at 8: s = 0; for i in 0 .. xs.count { s = s + (xs.first + i).value }; s (13) */
{C, 0, 0}, {LS, 1, 0}, {C, 0, 0}, {LC, XS, 0}, {LOOP, 0 | 7u << 16, 0},
- {LL, 1, 0}, {XF, XS, 0}, {LL, 0, 0}, {XM, 0, 0}, {IG, XS, 0}, {ADD, 0, 0}, {LS, 1, 0}, {LL, 1, 0},
+ {LL, 1, 0}, {XF, XS, 0}, {LL, 0, 0}, {XM, XS, 0}, {IG, XS, 0}, {ADD, 0, 0}, {LS, 1, 0}, {LL, 1, 0},
/* distinct at 21: for x in xs { if !seen.has(x.value) { seen.add(row { x.value }) } }; seen.count (10) */
{EACH_, WALKING(XS, 8), 0}, {ROW, XS, 0}, {IG, XS, 0}, {HAS_, SEEN, 0}, {NOT, 0, 0}, {JZ, 30, 0}, {ROW, XS, 0},
{IG, XS, 0}, {LA, SEEN, 0}, {LC, SEEN, 0},
@@ -6880,6 +6964,7 @@ int main(void) {
nested_replace();
nested_rows();
bytes_fields();
+ anchors();
memory_budget();
work_budget();
fold_mutations();