diff options
| author | eunhhu <[email protected]> | 2026-10-18 20:01:08 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-18 20:01:08 +0100 |
| commit | 936c08e3424c606855051ee86f25a7df0f162c62 (patch) | |
| tree | 94870b4892d616e420bf259f3031f960c4bf28cc | |
| parent | b22b8c3f6c5378b47c36370225d7c41d4394bb7c (diff) | |
| download | pure-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
| -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 | ||||
| -rw-r--r-- | docs/design/008-module-format.md | 108 | ||||
| -rw-r--r-- | docs/design/027-computation-core.md | 35 | ||||
| -rw-r--r-- | research/experiments/040-against-c/2026-10-18-counts-s33b.jsonl | 6 | ||||
| -rw-r--r-- | research/experiments/040-against-c/2026-10-18-counts-s34.jsonl | 6 | ||||
| -rw-r--r-- | research/experiments/041-batches/2026-10-18-counts.jsonl | 10 |
12 files changed, 345 insertions, 126 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(); diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index 9d5ad1f..3028011 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -22,16 +22,17 @@ v6은 **bool**을 수와 다른 스택 종류로 더한다([016](016-bool-and-i6 v6에는 뒤에 **i64**가 더해졌다([016](016-bool-and-i64.md)의 2단계): 새 명령, 새 타입 번호, 0이던 자리의 값(CONST와 NUM_TEXT의 인자 2, 집계의 PURE_FOLD_INTEGER)이라 판은 그대로다. 그 뒤 [027](027-computation-core.md)의 1단계로 **비트 연산, 자리의 바이트, i64 리스트의 바이트, 자리로 찾은 항목**이 더해졌다(새 명령 -넷, v7의 87–90). 2a로 **바이트 자르기, 한 바이트, 이름으로 담은 바이트와 text**(91–94), CONCAT의 바이트, NUM_TEXT의 형식이 -더해졌다. 2b로 **레코드 안의 리스트**(레코드 필드의 타입 4, 95–97), 레지스터(리스트 상태의 플래그 512), 경로 이름의 레코드 필드, -**수 읽기**(98)가 더해졌다. 2c로 **함수**(CALL·RETURN, 99–100, 머리말의 함수 수와 함수 표)와 **글자**(CHAR_AT·CHAR, 101–102)가 +넷, v7의 81–84). 2a로 **바이트 자르기, 한 바이트, 이름으로 담은 바이트와 text**(85–88), CONCAT의 바이트, NUM_TEXT의 형식이 +더해졌다. 2b로 **레코드 안의 리스트**(레코드 필드의 타입 4, 89–91), 레지스터(리스트 상태의 플래그 512), 경로 이름의 레코드 필드, +**수 읽기**(92)가 더해졌다. 2c로 **함수**(CALL·RETURN, 93–94, 머리말의 함수 수와 함수 표)와 **글자**(CHAR_AT·CHAR, 95–96)가 더해졌다. 판은 그대로다(0이던 머리말 자리에 함수 수, 함수가 없는 모듈은 배치가 같다). 3단계로 **저장과 transient 리스트의 레코드도 리스트를 담는다** (아래 "레코드 안의 리스트"). 모듈의 형식은 바뀌지 않고, 리스트의 이미지 머리가 레코드 그래프를 적는다. v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를 없앴다: 컬렉션 표, DOC·DOC_SIZE·REPLACE, 키 있는 메시지(입력 항목의 바이트 5는 이제 0)와 기준 리비전. 키 있는 리스트가 그 일을 한다(바이트를 담는 레코드 필드, 앱이 가진 리비전). 머리가 -38바이트로 줄었고(컬렉션 수가 빠졌다), LEN부터의 명령 번호가 당겨졌다(아래 표). 4단계가 끝날 때까지 v7은 걸음마다 바뀐다(1.0 -전, 생산자는 aup 하나다). +38바이트로 줄었고(컬렉션 수가 빠졌다), LEN부터의 명령 번호가 당겨졌다(아래 표). 4c로 **커서와 인덱스가 앵커 하나**가 되었다: +상태 종류 2가 text나 리스트 안의 자리이고(bool은 4, i64는 5), 명령 일곱(ANCHOR_*)이 커서와 인덱스의 열셋을 대신한다. 로드가 그것을 +대상에 맞는 런타임의 명령으로 바꿔 쓴다(module-run.h). 4단계가 끝날 때까지 v7은 걸음마다 바뀐다(1.0 전, 생산자는 aup 하나다). **레코드 안의 리스트**(027의 2b). - 레코드 필드의 타입 4(LIST)는 항목들의 레코드를 기본값 자리(i64)에 둔다. @@ -77,7 +78,7 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를 | 8 | u16 상태, u16 입력, u16 보기, u16 핸들러 | | 16 | u32 명령 수, u32 문자열 바이트 수 | | 24 | u16 필드, u16 상수, u16 레코드, u32 상수 블롭 바이트 수, u16 레코드 필드, u16 함수 | -| 38 | 상태 × 16 B: u32 이름, u32 타입(비트 0–7: 0 수, 1 text, 2 커서, 3 리스트, 4 인덱스, 5 bool, 6 i64. 비트 8: 저장하지 않는 상태(transient, [018](018-lifetimes-and-computation.md)), 그 위는 0. 메시지의 행 목록은 transient가 아니고, transient text나 리스트를 가리키는 커서와 인덱스는 transient다), 8 B 값: 수와 bool은 f64 초기값(bool은 0이나 1), i64는 초기값(부호 있는 little-endian), text는 u32 초기 상수, 커서는 u32 대상 text 상태와 u32 플래그(1이면 마크: 그 자리에 넣은 글 앞에 남는다), 리스트는 u32 레코드와 u32 키(0이면 없음, 아니면 키 필드 + 1, 256이면 메시지의 행 목록: 이름은 그 필드의 경로 `입력.필드`이고 키가 없다), 인덱스는 u32 대상 리스트 상태 | +| 38 | 상태 × 16 B: u32 이름, u32 타입(비트 0–7: 0 수, 1 text, 2 앵커(text나 리스트 안의 자리, 027의 4c), 3 리스트, 4 bool, 5 i64. 비트 8: 저장하지 않는 상태(transient, [018](018-lifetimes-and-computation.md)), 그 위는 0. 메시지의 행 목록은 transient가 아니고, transient text나 리스트를 가리키는 앵커는 transient다), 8 B 값: 수와 bool은 f64 초기값(bool은 0이나 1), i64는 초기값(부호 있는 little-endian), text는 u32 초기 상수, 앵커는 u32 대상(text나 리스트 상태)과 u32 플래그(text 안이면 1이 마크: 그 자리에 넣은 글 앞에 남는다. 리스트 안이면 0), 리스트는 u32 레코드와 u32 키(0이면 없음, 아니면 키 필드 + 1, 256이면 메시지의 행 목록: 이름은 그 필드의 경로 `입력.필드`이고 키가 없다) | | | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지, 3 text, 4 타이머, 5 출력: 앱이 보내는 메시지, 모양은 메시지와 같고 핸들러가 없다, 6 인자: 한 보기가 받는 인자, 모양은 메시지와 같고 핸들러가 없으며 이름은 `보기.args`), u8 키별 여부, u16 0, u32 키 이름(키 입력)·주기(타이머, 1 ms–24시간을 ms로)·보기 번호(인자) 또는 0, u16 첫 필드, u16 필드 수 | | | 필드 × 8 B: u32 이름(식별자, 또는 그룹 안의 필드면 점으로 이은 경로 `user.name`), u32 타입(1 u32le, 2 바이트, 3 수(binary64), 4 text, 5 행: 비트 8–23에 그 행이 채우는 행 목록 상태, 6 bool: 1바이트 0이나 1, 7 i64: 8바이트 부호 있는 little-endian). 페이로드는 접두부와 가변 필드의 바이트다. 접두부에는 선언 순서로 고정 필드의 값과 가변 필드(바이트·text)의 u32 길이가 들어가는데, 마지막 가변 필드는 길이 없이 나머지를 차지한다. 가변 필드의 바이트는 접두부 뒤에 선언 순서로 이어진다(가변 필드가 하나면 예전의 "바이트 꼬리"와 같다) | | | 상수 × 8 B: u32 블롭 오프셋, u32 길이 (UTF-8) | @@ -121,68 +122,63 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를 | 30 | NUM_TEXT | n → t | 가장 짧은 왕복 10진수. 1e15 미만 정수는 소수점·지수 없이, −0은 `0`. `인자`의 비트 0–1이 1이면 bool → t: `true`·`false`, 2면 i64. 그 위는 형식([027](027-computation-core.md)): 비트 8–15 폭(왼쪽을 채움), 16 부호 뒤를 0으로 채움, 17–19 꼴(PureFormat: 10진, 16진, 대문자 16진, 2진, 8진, 고정 소수), 20–24 고정 소수의 자릿수 + 1. 정수의 꼴에 정수가 아닌 수는 `limit` | | 31 | CONCAT | t t → t 또는 b b → b | `인자` 1이면 바이트끼리 | | 32–34 | CHARS LINES TEXT_BYTES | t → n | 글자(Unicode scalar) 수, 줄 수(줄바꿈 + 1), 바이트 수 | -| 35 | CUR_LOAD | → c | 커서 상태 `인자` | -| 36–37 | CUR_START CUR_END | → c | text 상태 `인자`의 처음·끝 | -| 38 | CUR_MOVE | c n → c | 단위 `인자`(0 글자, 1 줄)로 n만큼. 끝에서 멈춘다. 줄 이동은 열을 유지 | -| 39 | CUR_STORE | c → | 커서 상태 `인자`. 핸들러 전용 | +| 35 | ANCHOR_LOAD | → a | 앵커 상태 `인자`(text 안이면 c, 리스트 안이면 i) | +| 36–37 | ANCHOR_START ANCHOR_END | → a | `인자`가 text면 그 처음·끝(c), 리스트면 첫·마지막 항목(i, 빈 리스트면 0) | +| 38 | ANCHOR_MOVE | a n → a | `인자`의 비트 0–15는 앵커가 든 text나 리스트, 16–23은 단위(text: 0 글자, 1 줄. 리스트: 0 항목). n만큼, 정수가 아니면 `limit`. 끝에서 멈춘다. 줄 이동은 열을 유지 | +| 39 | ANCHOR_STORE | a → | 앵커 상태 `인자`. 핸들러 전용. 같은 이벤트에서 지우기 전에 얻은, 끝을 넘은 인덱스는 `not_found` | | 40 | TEXT_INSERT | c t → | text `인자`의 커서 위치에 넣는다 | | 41 | TEXT_REMOVE | c c → | 두 커서 사이를 지운다 | | 42 | TEXT_LINE | c → t | 커서가 있는 줄(줄바꿈 제외) | | 43 | TEXT_SET | t → | text 전체를 바꾸고 그 text의 커서를 처음으로 | | 44 | TEXT_FROM | b → t | UTF-8이 아니면 이벤트가 실패(encoding) | | 45 | BYTES_FROM | t → b | 같은 바이트 | -| 46 | CUR_COUNT | c → n | 단위 `인자`로 커서 앞의 글자 수, 또는 1부터 센 줄 번호 | +| 46 | ANCHOR_COUNT | a → n | 앵커 앞의 단위 수(`인자`는 MOVE와 같다): 글자 수, 0부터 센 줄, 0부터 센 항목의 자리 | | 47 | LIST_COUNT | → n | 리스트 상태 `인자`의 항목 수 | | 48 | LIST_APPEND | f… → | 레코드 필드 값을 필드 순서대로 받아 끝에 붙인다. 키 있는 리스트에서는 키 순서의 자리에 넣고, 그 키가 이미 있으면 `exists`. 핸들러 전용 | | 49 | LIST_INSERT | i f… → | 인덱스의 항목 앞에 넣는다(빈 리스트면 처음). 키 있는 리스트에는 쓸 수 없다 | | 50 | LIST_REMOVE | i → | 인덱스의 항목을 지운다. 없으면 `not_found` | | 51 | ITEM_GET | i → f | 항목의 필드. `인자`의 아래 16비트는 리스트 상태, 위는 필드 번호. 없으면 `not_found` | | 52 | ITEM_SET | i f → | 항목의 필드를 바꾼다. 키 있는 리스트의 키 필드는 바꿀 수 없다. 핸들러 전용 | -| 53 | INDEX_LOAD | → i | 인덱스 상태 `인자` | -| 54 | INDEX_STORE | i → | 인덱스 상태 `인자`. 핸들러 전용. 같은 이벤트에서 지우기 전에 얻은, 끝을 넘은 인덱스는 `not_found` | -| 55 | INDEX_MOVE | i n → i | n만큼. 정수가 아니면 `limit`. 처음과 마지막 항목에서 멈춘다 | -| 56–57 | INDEX_FIRST INDEX_LAST | → i | 리스트 상태 `인자`의 첫·마지막 항목(빈 리스트면 0) | -| 58 | INDEX_NUM | i → n | 0부터 센 위치 | -| 59 | ROW | → i | 열이 계산하는 행. `인자`는 그 표의 리스트 상태. 열 전용 | -| 60 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 | -| 61 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 | -| 62 | FOLD | → n 또는 i | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min, 4 first, 5 distinct: 포함된 항목의 서로 다른 값의 수, −0은 0), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 참을 남긴 항목을 세고, sum·max·min·distinct는 블록이 남긴 수와 포함 여부(bool)로 계산한다. first는 블록이 참을 남긴 첫 항목의 인덱스(그 리스트의 인덱스)이고 거기서 멈춘다. 포함된 항목이 없으면 count·sum은 0, max·min·first는 `not_found` | -| 63 | TEXT_COMPARE | t t → bool | 바이트 순서로 비교한다. `인자`는 관계(0 LT, 1 LE, 2 GT, 3 GE, 4 EQ, 5 NE, 수 비교 명령의 순서). 앞이 같으면 짧은 쪽이 앞이다 | -| 64 | LIST_FIND | k → i | 키 있는 리스트 `인자`에서 키가 k인 항목의 인덱스. 반씩 나눠 찾는다. 없으면 `not_found` | -| 65 | LIST_HAS | k → bool | 그 키의 항목이 있으면 참 | -| 66 | NOW | → n | 이벤트가 보는 지금(1970년부터 ms, UTC). 호스트가 정하고 스크립트는 아무 때나 재현한다. 핸들러 전용: 보기는 상태의 순수 함수로 남는다 | -| 67 | FLOOR | n → n | 내림 | -| 68 | MOD | n n → n | 나눗셈을 내림한 나머지. 결과는 나누는 수의 부호를 따른다(0도). 0으로 나누면 `divide` | -| 69 | SEND | f… → | 출력 `인자`의 필드 값을 필드 순서대로 받아(u32·수는 n, bool은 bool, 바이트는 b, text는 t) 메시지 페이로드(접두부와 가변 필드)로 만들고 이벤트의 일부로 보낸다. u32 필드의 값이 0–2³²−1의 정수가 아니면 `limit`. 핸들러 전용 | -| 70 | OUTER_ROW | → i | 열 안의 집계 블록에서 그 열의 행(집계의 항목은 ROW). 걷기 안의 집계에서는 걷는 항목 | -| 71 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 걷기와 LOOP는 서로 안에 8겹까지 둔다. 안쪽의 코드는 바깥 걷기가 도는 리스트에 넣거나 지우지 못한다. 핸들러 전용 | -| 72 | LOCAL_LOAD | → n, bool 또는 i | 이벤트의 지역 칸. `인자`의 비트 0–7은 칸(64개), 8–15는 담는 것(0 수, 1 인덱스, 2 bool), 16–31은 인덱스의 리스트. 칸은 이벤트가 시작할 때 0이고 저장되지 않는다. 핸들러와 그 안의 걷기, 그 안의 집계에서 읽는다 | -| 73 | LOCAL_STORE | n, bool, i64 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 담는 것 3은 i64. 한 핸들러는 한 칸을 한 가지(수, bool, i64, 또는 한 리스트의 인덱스)로만 쓴다 | -| 74–76 | I64_ADD I64_SUB I64_MUL | i64 i64 → i64 | 범위를 넘으면 `overflow` | -| 77–78 | I64_DIV I64_MOD | i64 i64 → i64 | 내림한 몫과 나머지(나머지는 나누는 수의 부호). 0으로 나누면 `divide`, 가장 작은 i64 / −1은 `overflow` | -| 79 | I64_NEG | i64 → i64 | 가장 작은 i64는 `overflow` | -| 80 | I64_COMPARE | i64 i64 → bool | `인자`는 관계(TEXT_COMPARE와 같다) | -| 81–82 | I64_MIN I64_MAX | i64 i64 → i64 | | -| 83 | I64_FROM | n → i64 | 범위 안의 정수만. 아니면 `limit` | -| 84 | NUM_FROM | i64 → n | \|i\| ≤ 2^53일 때만(정확하다). 아니면 `limit` | -| 85 | LOOP | n n 또는 i i → | 세는 반복([018](018-lifetimes-and-computation.md)). `인자`의 비트 0–7은 지역 칸, 비트 8은 i64의 반복, 16–31은 뒤따르는 블록의 길이 L. 낮음과 높음을 꺼내 그 사이의 값마다(낮음부터 높음 앞까지 1씩) 칸에 값을 두고 L개 명령을 돈다. 수는 2^53 안의 정수여야 하고 아니면 `limit`이다. 블록은 둘레 블록의 코드로 돈다(걷기 안이면 `ROW`도 그대로). 실행량은 돌 때마다 L + 1을 센다. 핸들러 전용 | -| 86 | BREAK | | 그것이 든 가장 안쪽의 LOOP나 EACH를 끝낸다(그 블록 안, 조건 안에서도). 스택이 빈 자리에서만. 핸들러 전용 | -| 87 | I64_BITS | i64 i64 → i64 | 끝없는 2의 보수 위의 정수 연산([027](027-computation-core.md)). `인자` 0 and, 1 or, 2 xor, 3 왼쪽 시프트(a·2^b, 범위를 넘으면 `overflow`), 4 오른쪽 시프트(floor(a / 2^b)). 시프트 수가 0–63 밖이면 `limit` | -| 88 | BYTE_AT | b n 또는 b i64 → i64 | 바이트 값의 그 자리 바이트(0–255). `인자` 0이면 자리가 수, 1이면 i64. 정수가 아니거나 값 밖이면 `limit` | -| 89 | BYTES_OF | → b | 리스트 상태 `인자`(레코드가 i64 필드 하나)의 항목들을 한 바이트씩. 0–255 밖의 값은 `limit` | -| 90 | INDEX_AT | n → i | 리스트 상태 `인자`의 그 자리 항목의 인덱스. 정수가 아니면 `limit`, 그 자리에 항목이 없으면 `not_found` | -| 91 | BYTES_SLICE | b n n 또는 b i64 i64 → b | a부터 c 앞까지의 바이트. `인자` 1이면 자리가 i64. 둘 다 정수이고 0 ≤ a ≤ c ≤ 길이가 아니면 `limit` | -| 92 | BYTE | i64 → b | 한 바이트. 0–255 밖이면 `limit` | -| 93 | BYTES_LOAD | → b 또는 t | 이름으로 담은 값 `인자`(0–15 바이트, 16–31 text). 이벤트와 보기의 본문이 시작할 때 비어 있다 | -| 94 | BYTES_STORE | b 또는 t → | 이름으로 담은 값 `인자`의 비트 0–4를 그 값으로(비트 8이면 뒤에 덧붙여). 담은 바이트는 이벤트의 몫에 세고, 넘으면 `memory`. text 칸에는 text만 | -| 95 | LIST_VALUE | → l | 이벤트의 리스트 상태 `인자`의 값(항목의 복사, 빈 리스트는 0). 아래 "레코드 안의 리스트" | -| 96 | LIST_BIND | l → | 묶이는 리스트 상태 `인자`(레지스터)가 그 리스트를 읽게 한다 | -| 97 | APPEND_IN | i f… → | 리스트 상태 `인자`의 비트 0–15(이벤트의 리스트)에서 인덱스의 항목, 그 필드(비트 16–31, 리스트 필드)에 담긴 리스트에 항목을 덧붙인다. 그 리스트를 다른 값이 나눠 가지면 먼저 제 것으로 복사한다 | -| 98 | NUM_PARSE | t → n | JSON의 수로 쓴 text의 가장 가까운 수. 그런 text가 아니거나 유한하지 않으면 `limit` | -| 99 | CALL | a… → r… | 함수 `인자`를 부른다. 매개변수를 꺼내고 결과를 놓는다. 위 "함수" | -| 100 | RETURN | … r… → | 함수 `인자`(자신)를 끝낸다. 맨 위의 결과가 부른 쪽으로 간다 | -| 101 | CHAR_AT | t q → q | text의 바이트 자리 q(i64)에서 시작하는 글자의 코드 포인트(i64). 글자가 시작하지 않는 자리나 밖이면 `limit` | -| 102 | CHAR | q → t | 코드 포인트 하나의 text. 0–0x10FFFF 밖이면 `limit`, 서로게이트면 `encoding` | +| 53 | ROW | → i | 열이 계산하는 행. `인자`는 그 표의 리스트 상태. 열 전용 | +| 54 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 | +| 55 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 | +| 56 | FOLD | → n 또는 i | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min, 4 first, 5 distinct: 포함된 항목의 서로 다른 값의 수, −0은 0), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 참을 남긴 항목을 세고, sum·max·min·distinct는 블록이 남긴 수와 포함 여부(bool)로 계산한다. first는 블록이 참을 남긴 첫 항목의 인덱스(그 리스트의 인덱스)이고 거기서 멈춘다. 포함된 항목이 없으면 count·sum은 0, max·min·first는 `not_found` | +| 57 | TEXT_COMPARE | t t → bool | 바이트 순서로 비교한다. `인자`는 관계(0 LT, 1 LE, 2 GT, 3 GE, 4 EQ, 5 NE, 수 비교 명령의 순서). 앞이 같으면 짧은 쪽이 앞이다 | +| 58 | LIST_FIND | k → i | 키 있는 리스트 `인자`에서 키가 k인 항목의 인덱스. 반씩 나눠 찾는다. 없으면 `not_found` | +| 59 | LIST_HAS | k → bool | 그 키의 항목이 있으면 참 | +| 60 | NOW | → n | 이벤트가 보는 지금(1970년부터 ms, UTC). 호스트가 정하고 스크립트는 아무 때나 재현한다. 핸들러 전용: 보기는 상태의 순수 함수로 남는다 | +| 61 | FLOOR | n → n | 내림 | +| 62 | MOD | n n → n | 나눗셈을 내림한 나머지. 결과는 나누는 수의 부호를 따른다(0도). 0으로 나누면 `divide` | +| 63 | SEND | f… → | 출력 `인자`의 필드 값을 필드 순서대로 받아(u32·수는 n, bool은 bool, 바이트는 b, text는 t) 메시지 페이로드(접두부와 가변 필드)로 만들고 이벤트의 일부로 보낸다. u32 필드의 값이 0–2³²−1의 정수가 아니면 `limit`. 핸들러 전용 | +| 64 | OUTER_ROW | → i | 열 안의 집계 블록에서 그 열의 행(집계의 항목은 ROW). 걷기 안의 집계에서는 걷는 항목 | +| 65 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 걷기와 LOOP는 서로 안에 8겹까지 둔다. 안쪽의 코드는 바깥 걷기가 도는 리스트에 넣거나 지우지 못한다. 핸들러 전용 | +| 66 | LOCAL_LOAD | → n, bool 또는 i | 이벤트의 지역 칸. `인자`의 비트 0–7은 칸(64개), 8–15는 담는 것(0 수, 1 인덱스, 2 bool), 16–31은 인덱스의 리스트. 칸은 이벤트가 시작할 때 0이고 저장되지 않는다. 핸들러와 그 안의 걷기, 그 안의 집계에서 읽는다 | +| 67 | LOCAL_STORE | n, bool, i64 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 담는 것 3은 i64. 한 핸들러는 한 칸을 한 가지(수, bool, i64, 또는 한 리스트의 인덱스)로만 쓴다 | +| 68–70 | I64_ADD I64_SUB I64_MUL | i64 i64 → i64 | 범위를 넘으면 `overflow` | +| 71–72 | I64_DIV I64_MOD | i64 i64 → i64 | 내림한 몫과 나머지(나머지는 나누는 수의 부호). 0으로 나누면 `divide`, 가장 작은 i64 / −1은 `overflow` | +| 73 | I64_NEG | i64 → i64 | 가장 작은 i64는 `overflow` | +| 74 | I64_COMPARE | i64 i64 → bool | `인자`는 관계(TEXT_COMPARE와 같다) | +| 75–76 | I64_MIN I64_MAX | i64 i64 → i64 | | +| 77 | I64_FROM | n → i64 | 범위 안의 정수만. 아니면 `limit` | +| 78 | NUM_FROM | i64 → n | \|i\| ≤ 2^53일 때만(정확하다). 아니면 `limit` | +| 79 | LOOP | n n 또는 i i → | 세는 반복([018](018-lifetimes-and-computation.md)). `인자`의 비트 0–7은 지역 칸, 비트 8은 i64의 반복, 16–31은 뒤따르는 블록의 길이 L. 낮음과 높음을 꺼내 그 사이의 값마다(낮음부터 높음 앞까지 1씩) 칸에 값을 두고 L개 명령을 돈다. 수는 2^53 안의 정수여야 하고 아니면 `limit`이다. 블록은 둘레 블록의 코드로 돈다(걷기 안이면 `ROW`도 그대로). 실행량은 돌 때마다 L + 1을 센다. 핸들러 전용 | +| 80 | BREAK | | 그것이 든 가장 안쪽의 LOOP나 EACH를 끝낸다(그 블록 안, 조건 안에서도). 스택이 빈 자리에서만. 핸들러 전용 | +| 81 | I64_BITS | i64 i64 → i64 | 끝없는 2의 보수 위의 정수 연산([027](027-computation-core.md)). `인자` 0 and, 1 or, 2 xor, 3 왼쪽 시프트(a·2^b, 범위를 넘으면 `overflow`), 4 오른쪽 시프트(floor(a / 2^b)). 시프트 수가 0–63 밖이면 `limit` | +| 82 | BYTE_AT | b n 또는 b i64 → i64 | 바이트 값의 그 자리 바이트(0–255). `인자` 0이면 자리가 수, 1이면 i64. 정수가 아니거나 값 밖이면 `limit` | +| 83 | BYTES_OF | → b | 리스트 상태 `인자`(레코드가 i64 필드 하나)의 항목들을 한 바이트씩. 0–255 밖의 값은 `limit` | +| 84 | ANCHOR_AT | n → a | 처음부터 n 단위 뒤의 앵커(`인자`는 MOVE와 같다). 정수가 아니면 `limit`, 그 자리가 없으면 `not_found` | +| 85 | BYTES_SLICE | b n n 또는 b i64 i64 → b | a부터 c 앞까지의 바이트. `인자` 1이면 자리가 i64. 둘 다 정수이고 0 ≤ a ≤ c ≤ 길이가 아니면 `limit` | +| 86 | BYTE | i64 → b | 한 바이트. 0–255 밖이면 `limit` | +| 87 | BYTES_LOAD | → b 또는 t | 이름으로 담은 값 `인자`(0–15 바이트, 16–31 text). 이벤트와 보기의 본문이 시작할 때 비어 있다 | +| 88 | BYTES_STORE | b 또는 t → | 이름으로 담은 값 `인자`의 비트 0–4를 그 값으로(비트 8이면 뒤에 덧붙여). 담은 바이트는 이벤트의 몫에 세고, 넘으면 `memory`. text 칸에는 text만 | +| 89 | LIST_VALUE | → l | 이벤트의 리스트 상태 `인자`의 값(항목의 복사, 빈 리스트는 0). 아래 "레코드 안의 리스트" | +| 90 | LIST_BIND | l → | 묶이는 리스트 상태 `인자`(레지스터)가 그 리스트를 읽게 한다 | +| 91 | APPEND_IN | i f… → | 리스트 상태 `인자`의 비트 0–15(이벤트의 리스트)에서 인덱스의 항목, 그 필드(비트 16–31, 리스트 필드)에 담긴 리스트에 항목을 덧붙인다. 그 리스트를 다른 값이 나눠 가지면 먼저 제 것으로 복사한다 | +| 92 | NUM_PARSE | t → n | JSON의 수로 쓴 text의 가장 가까운 수. 그런 text가 아니거나 유한하지 않으면 `limit` | +| 93 | CALL | a… → r… | 함수 `인자`를 부른다. 매개변수를 꺼내고 결과를 놓는다. 위 "함수" | +| 94 | RETURN | … r… → | 함수 `인자`(자신)를 끝낸다. 맨 위의 결과가 부른 쪽으로 간다 | +| 95 | CHAR_AT | t q → q | text의 바이트 자리 q(i64)에서 시작하는 글자의 코드 포인트(i64). 글자가 시작하지 않는 자리나 밖이면 `limit` | +| 96 | CHAR | q → t | 코드 포인트 하나의 text. 0–0x10FFFF 밖이면 `limit`, 서로게이트면 `encoding` | bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이트의 정수다. CONST의 인자 2는 i64 상수(값의 8바이트), NUM_TEXT의 인자 2는 i64의 10진 글자다. LOAD·STORE·지역 칸·항목·FIELD·SEND는 i64를 수처럼 옮긴다. FOLD의 종류에 64(PURE_FOLD_INTEGER)를 diff --git a/docs/design/027-computation-core.md b/docs/design/027-computation-core.md index 239a784..b0a2971 100644 --- a/docs/design/027-computation-core.md +++ b/docs/design/027-computation-core.md @@ -299,11 +299,44 @@ - 구현은 대상의 종류로 나뉜다. 로드가 형식의 명령을 대상에 맞는 내부 명령(지금의 CUR_*와 INDEX_*의 걸음)으로 바꿔 쓰고, 검증은 그것에 대해 지금처럼 한다. 그래서 실행기, 묶음 실행기, JIT의 길은 그대로다. 026의 원칙(뜻은 일반적으로, 특수화는 구현 아래로)이다. + - **만듦(2026-10-18).** + - 형식: 상태 종류 2가 앵커다(bool 4, i64 5). 명령은 103개에서 97개가 되었다. 앵커 명령 일곱의 MOVE·COUNT·AT는 인자에 + 대상과 단위를 적는다(`PURE_ANCHOR_UNIT`). + - 로드: 앵커 상태는 대상이 text면 커서, 리스트면 인덱스로 둔다(리스트 안의 마크는 거절). 앵커 명령은 런타임의 명령 + (`PURE_OP_CUR_*`, `PURE_OP_INDEX_*`: module-run.h, 모듈 명령 다음 번호)으로 바뀐다. 모듈이 그 번호를 담으면 거절한다. + 검증기는 MOVE·COUNT·AT가 적은 대상과 피연산자의 주인이 같은지 본다. 3단계의 내부 명령(`PURE_OP_KEPT_*`)도 그 표로 옮겼다. + - 새 것: text의 AT(처음부터 n 글자나 n 줄 뒤의 커서, 끝을 넘으면 `not_found`). COUNT의 줄은 0부터 센다(aup의 `at.line`이 + 1을 더한다). + - aup는 컴파일러 안의 커서와 인덱스를 그대로 두고, 명령 번호와 상태 번호를 형식대로 낸다. + - 시험: 단위 시험 `anchors`(text의 AT와 COUNT, 리스트의 AT, 거절 넷). 시험의 모듈 짓기는 런타임의 명령 이름을 형식의 앵커 + 명령으로 옮겨 쓴다. + - 명령 수(b34 대 b33): 묶음 matmul −1.36%, knapsack −0.08%, BFS +0.03%, 실행기만은 ±0.04% 안이다. 은행 질의(s33은 4b-2의 aup + 모듈, s34는 4c의 것, 각 여섯 번의 중앙값)는 in_credit +0.28%, per_branch +0.23%, ranks +0.10%, 나머지 0.00%다. 이 셋은 같은 + 빌드에서도 1–2% 오간다. - **4d. 입력은 메시지 하나(의심 1).** - 키, text, 타이머, 프레임, 가리킴은 표면과 호스트가 만드는 메시지다. - 모듈의 입력은 메시지(필드들)와 표면이 읽는 묶음(키 이름, text, 주기, 프레임)이다. 묶음을 해석하는 것은 표면이고, Pure는 메시지만 받는다. - - `dt`는 프레임 메시지의 필드가 된다. 키의 누름과 뗌은 메시지의 bool 필드다. + - `dt`는 프레임 메시지의 필드가 된다. 키의 누름과 뗌은 각각의 메시지다. + - **설계(2026-10-18).** + - 입력 항목은 메시지, 출력, 인자의 셋이다. 메시지는 묶음(binding)을 하나 가질 수 있다. 묶음은 표면과 호스트에게 무엇이 + 그 메시지를 만드는지 알려 준다(묶음의 필드는 정해져 있다): + - 키 누름 `이름`, 키 뗌 `이름`: 그때마다 메시지 하나, 필드 없음(누름은 이름 붙은 동작이다: 접근성이 그것을 읽는다) + - text: 들어온 text마다, 필드 `text: text` + - 주기 `ms`: 틱마다, 필드 없음(시각은 늘 그렇듯 `now`) + - 프레임: 화면의 한 장마다, 필드 `dt: number` + - 가리킴 `보기`: 표의 행을 가리킬 때, 필드 `position: u32` + - Pure의 코어는 묶음을 해석하지 않는다. 묶음의 필드가 맞는지만 검증한다. 핸들러의 이벤트는 입력의 번호다(누름과 뗌의 두 + 이벤트, 프레임의 이벤트 0이 없다). + - 빠지는 것: 입력 종류 키·text·타이머, 프레임 이벤트(이벤트 0), `pure_app_input`·`tick`·`text`·`frame`·`point`, 명령 DT와 + TEXT_INPUT(필드 읽기가 한다). 호스트는 묶음대로 메시지를 짓는다. + - 저장: 프레임 메시지도 다른 메시지처럼 저장한다. 초당 60번 저장하지 않으려면 호스트가 묶음 저장을 쓴다(Vega는 프레임이 아닌 + 입력 뒤에 저장한다). Pure는 어느 메시지가 프레임인지 모른다. + - aup: 겉은 그대로다. `input up = key ArrowUp`의 `on up.down { … }`은 누름 메시지의 핸들러로, `on up.up { … }`은 숨은 뗌 입력의 + 핸들러로, + `on frame`과 `dt`는 숨은 프레임 입력과 그 필드로, `input typed = text`의 `typed`는 그 필드로 낸다. `view X_focus = + at.position`은 숨은 가리킴 입력과 `at`을 옮기는 핸들러를 낸다(그래서 앱이 바뀌지 않는다). + - Vega: 키, text, 타이머, 프레임, 가리킴을 묶음을 보고 메시지로 보낸다. serve: 키와 text의 POST를 메시지로, 타이머를 주기 묶음으로. - **4e. 보기는 아무 값(의심 2).** - 보기의 타입은 레코드 필드의 타입이다. 표는 레코드의 리스트를 내는 보기이고, 열은 그 레코드의 필드다. - 게으른 칸 계산과 한 번 훑는 집계는 그 구현으로 남는다. 항목이 리스트를 담을 수 있고, 보기의 본문이 지은 리스트도 낼 수 있다. diff --git a/research/experiments/040-against-c/2026-10-18-counts-s33b.jsonl b/research/experiments/040-against-c/2026-10-18-counts-s33b.jsonl new file mode 100644 index 0000000..9ba9692 --- /dev/null +++ b/research/experiments/040-against-c/2026-10-18-counts-s33b.jsonl @@ -0,0 +1,6 @@ +{"serve": "pure-serve", "per_branch": {"instructions": 27092798, "cycles": 11831078}, "in_credit": {"instructions": 3514749, "cycles": 1575505}, "large": {"instructions": 1429143, "cycles": 603570}, "ranks": {"instructions": 24476624, "cycles": 14838966}, "none": {"instructions": 617940, "cycles": 192835}, "field": {"instructions": 9090159, "cycles": 3312030}, "found": {"instructions": 14242457, "cycles": 10695889}} +{"serve": "pure-serve", "per_branch": {"instructions": 26985796, "cycles": 11789465}, "in_credit": {"instructions": 3490620, "cycles": 1589023}, "large": {"instructions": 1429103, "cycles": 603592}, "ranks": {"instructions": 24416772, "cycles": 15666964}, "none": {"instructions": 617940, "cycles": 221635}, "field": {"instructions": 9090056, "cycles": 3440075}, "found": {"instructions": 14241648, "cycles": 10955359}} +{"serve": "pure-serve", "per_branch": {"instructions": 27218504, "cycles": 11991540}, "in_credit": {"instructions": 3542805, "cycles": 1636061}, "large": {"instructions": 1429119, "cycles": 608753}, "ranks": {"instructions": 24544068, "cycles": 14924696}, "none": {"instructions": 617940, "cycles": 191430}, "field": {"instructions": 9090056, "cycles": 3306977}, "found": {"instructions": 14241321, "cycles": 10726056}} +{"serve": "pure-serve", "per_branch": {"instructions": 27181700, "cycles": 12070147}, "in_credit": {"instructions": 3534567, "cycles": 1461536}, "large": {"instructions": 1429111, "cycles": 595629}, "ranks": {"instructions": 24523809, "cycles": 14612800}, "none": {"instructions": 617940, "cycles": 192228}, "field": {"instructions": 9090056, "cycles": 3306667}, "found": {"instructions": 14241312, "cycles": 10747681}} +{"serve": "pure-serve", "per_branch": {"instructions": 27060091, "cycles": 11806774}, "in_credit": {"instructions": 3507242, "cycles": 1615446}, "large": {"instructions": 1429167, "cycles": 658102}, "ranks": {"instructions": 24457110, "cycles": 14860810}, "none": {"instructions": 617940, "cycles": 191797}, "field": {"instructions": 9090056, "cycles": 3297040}, "found": {"instructions": 14241904, "cycles": 10742638}} +{"serve": "pure-serve", "per_branch": {"instructions": 27075571, "cycles": 11832314}, "in_credit": {"instructions": 3510791, "cycles": 1558434}, "large": {"instructions": 1429127, "cycles": 617450}, "ranks": {"instructions": 24466185, "cycles": 14925149}, "none": {"instructions": 617940, "cycles": 191512}, "field": {"instructions": 9090056, "cycles": 3313202}, "found": {"instructions": 14243240, "cycles": 10773223}} diff --git a/research/experiments/040-against-c/2026-10-18-counts-s34.jsonl b/research/experiments/040-against-c/2026-10-18-counts-s34.jsonl new file mode 100644 index 0000000..7bf052e --- /dev/null +++ b/research/experiments/040-against-c/2026-10-18-counts-s34.jsonl @@ -0,0 +1,6 @@ +{"serve": "pure-serve", "per_branch": {"instructions": 27312042, "cycles": 11813236}, "in_credit": {"instructions": 3559926, "cycles": 2112527}, "large": {"instructions": 1429088, "cycles": 759103}, "ranks": {"instructions": 24586579, "cycles": 15395656}, "none": {"instructions": 617940, "cycles": 192129}, "field": {"instructions": 9090159, "cycles": 3295959}, "found": {"instructions": 14239585, "cycles": 10687211}} +{"serve": "pure-serve", "per_branch": {"instructions": 27103313, "cycles": 11781864}, "in_credit": {"instructions": 3512950, "cycles": 1601375}, "large": {"instructions": 1429183, "cycles": 604754}, "ranks": {"instructions": 24470721, "cycles": 14951287}, "none": {"instructions": 617940, "cycles": 192587}, "field": {"instructions": 9090056, "cycles": 3285574}, "found": {"instructions": 14243336, "cycles": 10632389}} +{"serve": "pure-serve", "per_branch": {"instructions": 27080858, "cycles": 11703491}, "in_credit": {"instructions": 3508016, "cycles": 1614149}, "large": {"instructions": 1429183, "cycles": 604993}, "ranks": {"instructions": 24459823, "cycles": 14920948}, "none": {"instructions": 617940, "cycles": 191384}, "field": {"instructions": 9090056, "cycles": 3302254}, "found": {"instructions": 14244176, "cycles": 10600993}} +{"serve": "pure-serve", "per_branch": {"instructions": 27189720, "cycles": 11867735}, "in_credit": {"instructions": 3532452, "cycles": 1607785}, "large": {"instructions": 1429055, "cycles": 607205}, "ranks": {"instructions": 24518908, "cycles": 14798036}, "none": {"instructions": 617940, "cycles": 190831}, "field": {"instructions": 9090159, "cycles": 3291538}, "found": {"instructions": 14238569, "cycles": 10617781}} +{"serve": "pure-serve", "per_branch": {"instructions": 27254770, "cycles": 14000284}, "in_credit": {"instructions": 3547134, "cycles": 1817409}, "large": {"instructions": 1429119, "cycles": 656814}, "ranks": {"instructions": 24555643, "cycles": 15096239}, "none": {"instructions": 617940, "cycles": 190648}, "field": {"instructions": 9090056, "cycles": 3295227}, "found": {"instructions": 14241528, "cycles": 10486588}} +{"serve": "pure-serve", "per_branch": {"instructions": 27096875, "cycles": 11761034}, "in_credit": {"instructions": 3511504, "cycles": 1577719}, "large": {"instructions": 1429191, "cycles": 613492}, "ranks": {"instructions": 24467678, "cycles": 14868083}, "none": {"instructions": 617940, "cycles": 200177}, "field": {"instructions": 9090056, "cycles": 3290970}, "found": {"instructions": 14244416, "cycles": 11538475}} diff --git a/research/experiments/041-batches/2026-10-18-counts.jsonl b/research/experiments/041-batches/2026-10-18-counts.jsonl index 3fdfe04..74a03d2 100644 --- a/research/experiments/041-batches/2026-10-18-counts.jsonl +++ b/research/experiments/041-batches/2026-10-18-counts.jsonl @@ -88,3 +88,13 @@ {"version":"b33","way":"rounds","work":"join","instructions":7680473996,"cycles":3365659328} {"version":"b33","way":"batches","work":"bfs","instructions":3345467558,"cycles":1676207366} {"version":"b33","way":"rounds","work":"bfs","instructions":4703743508,"cycles":2071781377} +{"version":"b34","way":"batches","work":"matmul","instructions":703679979,"cycles":230315537} +{"version":"b34","way":"rounds","work":"matmul","instructions":21259729955,"cycles":6404749040} +{"version":"b34","way":"batches","work":"knapsack","instructions":348346717,"cycles":105544847} +{"version":"b34","way":"rounds","work":"knapsack","instructions":21991046277,"cycles":7478248902} +{"version":"b34","way":"batches","work":"roots","instructions":157260930,"cycles":109354216} +{"version":"b34","way":"rounds","work":"roots","instructions":2719818706,"cycles":953160118} +{"version":"b34","way":"batches","work":"join","instructions":1783682805,"cycles":1035759776} +{"version":"b34","way":"rounds","work":"join","instructions":7677476459,"cycles":3286100652} +{"version":"b34","way":"batches","work":"bfs","instructions":3346476311,"cycles":1665790741} +{"version":"b34","way":"rounds","work":"bfs","instructions":4703444335,"cycles":2053412578} |
