aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
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();