diff options
Diffstat (limited to 'core')
| -rw-r--r-- | core/module-jit.c | 2 | ||||
| -rw-r--r-- | core/module-run.h | 12 | ||||
| -rw-r--r-- | core/module-text.c | 2 | ||||
| -rw-r--r-- | core/module-text.h | 2 | ||||
| -rw-r--r-- | core/module.c | 125 | ||||
| -rw-r--r-- | core/module.h | 26 | ||||
| -rw-r--r-- | core/test-module.c | 137 |
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(); |
